mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-13 14:29:35 +00:00
Merge branch 'main' into update-use-secret-path-across-app
This commit is contained in:
@@ -11,9 +11,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
|
||||
|
||||
+2
-2
@@ -17,7 +17,7 @@ WORKDIR /app
|
||||
ENV npm_config_cache /home/node/.npm
|
||||
|
||||
COPY package*.json ./
|
||||
RUN npm ci --only-production
|
||||
RUN npm ci --only-production && npm cache clean --force
|
||||
|
||||
COPY --from=build /app .
|
||||
|
||||
@@ -30,4 +30,4 @@ HEALTHCHECK --interval=10s --timeout=3s --start-period=10s \
|
||||
|
||||
EXPOSE 4000
|
||||
|
||||
CMD ["npm", "run", "start"]
|
||||
CMD ["node", "build/index.js"]
|
||||
|
||||
Generated
+74
-79
@@ -37,6 +37,7 @@
|
||||
"handlebars": "^4.7.7",
|
||||
"helmet": "^5.1.1",
|
||||
"infisical-node": "^1.2.1",
|
||||
"ioredis": "^5.3.2",
|
||||
"js-yaml": "^4.1.0",
|
||||
"jsonwebtoken": "^9.0.0",
|
||||
"jsrp": "^0.2.4",
|
||||
@@ -3508,8 +3509,7 @@
|
||||
"node_modules/@ioredis/commands": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@ioredis/commands/-/commands-1.2.0.tgz",
|
||||
"integrity": "sha512-Sx1pU8EM64o2BrqNpEO1CNLtKQwyhuXuqyfH7oGKCk+1a33d2r5saW8zNwm3j6BTExtjrv2BxTgzzkMwts6vGg==",
|
||||
"dev": true
|
||||
"integrity": "sha512-Sx1pU8EM64o2BrqNpEO1CNLtKQwyhuXuqyfH7oGKCk+1a33d2r5saW8zNwm3j6BTExtjrv2BxTgzzkMwts6vGg=="
|
||||
},
|
||||
"node_modules/@istanbuljs/load-nyc-config": {
|
||||
"version": "1.1.0",
|
||||
@@ -7031,39 +7031,6 @@
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/bull/node_modules/denque": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz",
|
||||
"integrity": "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=0.10"
|
||||
}
|
||||
},
|
||||
"node_modules/bull/node_modules/ioredis": {
|
||||
"version": "5.3.2",
|
||||
"resolved": "https://registry.npmjs.org/ioredis/-/ioredis-5.3.2.tgz",
|
||||
"integrity": "sha512-1DKMMzlIHM02eBBVOFQ1+AolGjs6+xEcM4PDL7NqOS6szq7H9jSaEkIUH6/a5Hl241LzW6JLSiAbNvTQjUupUA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@ioredis/commands": "^1.1.1",
|
||||
"cluster-key-slot": "^1.1.0",
|
||||
"debug": "^4.3.4",
|
||||
"denque": "^2.1.0",
|
||||
"lodash.defaults": "^4.2.0",
|
||||
"lodash.isarguments": "^3.1.0",
|
||||
"redis-errors": "^1.2.0",
|
||||
"redis-parser": "^3.0.0",
|
||||
"standard-as-callback": "^2.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.22.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/ioredis"
|
||||
}
|
||||
},
|
||||
"node_modules/bytes": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
|
||||
@@ -9071,30 +9038,36 @@
|
||||
}
|
||||
},
|
||||
"node_modules/ioredis": {
|
||||
"version": "4.28.5",
|
||||
"resolved": "https://registry.npmjs.org/ioredis/-/ioredis-4.28.5.tgz",
|
||||
"integrity": "sha512-3GYo0GJtLqgNXj4YhrisLaNNvWSNwSS2wS4OELGfGxH8I69+XfNdnmV1AyN+ZqMh0i7eX+SWjrwFKDBDgfBC1A==",
|
||||
"version": "5.3.2",
|
||||
"resolved": "https://registry.npmjs.org/ioredis/-/ioredis-5.3.2.tgz",
|
||||
"integrity": "sha512-1DKMMzlIHM02eBBVOFQ1+AolGjs6+xEcM4PDL7NqOS6szq7H9jSaEkIUH6/a5Hl241LzW6JLSiAbNvTQjUupUA==",
|
||||
"dependencies": {
|
||||
"@ioredis/commands": "^1.1.1",
|
||||
"cluster-key-slot": "^1.1.0",
|
||||
"debug": "^4.3.1",
|
||||
"denque": "^1.1.0",
|
||||
"debug": "^4.3.4",
|
||||
"denque": "^2.1.0",
|
||||
"lodash.defaults": "^4.2.0",
|
||||
"lodash.flatten": "^4.4.0",
|
||||
"lodash.isarguments": "^3.1.0",
|
||||
"p-map": "^2.1.0",
|
||||
"redis-commands": "1.7.0",
|
||||
"redis-errors": "^1.2.0",
|
||||
"redis-parser": "^3.0.0",
|
||||
"standard-as-callback": "^2.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
"node": ">=12.22.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/ioredis"
|
||||
}
|
||||
},
|
||||
"node_modules/ioredis/node_modules/denque": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz",
|
||||
"integrity": "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==",
|
||||
"engines": {
|
||||
"node": ">=0.10"
|
||||
}
|
||||
},
|
||||
"node_modules/ip": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ip/-/ip-2.0.0.tgz",
|
||||
@@ -14445,6 +14418,31 @@
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/probot/node_modules/ioredis": {
|
||||
"version": "4.28.5",
|
||||
"resolved": "https://registry.npmjs.org/ioredis/-/ioredis-4.28.5.tgz",
|
||||
"integrity": "sha512-3GYo0GJtLqgNXj4YhrisLaNNvWSNwSS2wS4OELGfGxH8I69+XfNdnmV1AyN+ZqMh0i7eX+SWjrwFKDBDgfBC1A==",
|
||||
"dependencies": {
|
||||
"cluster-key-slot": "^1.1.0",
|
||||
"debug": "^4.3.1",
|
||||
"denque": "^1.1.0",
|
||||
"lodash.defaults": "^4.2.0",
|
||||
"lodash.flatten": "^4.4.0",
|
||||
"lodash.isarguments": "^3.1.0",
|
||||
"p-map": "^2.1.0",
|
||||
"redis-commands": "1.7.0",
|
||||
"redis-errors": "^1.2.0",
|
||||
"redis-parser": "^3.0.0",
|
||||
"standard-as-callback": "^2.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/ioredis"
|
||||
}
|
||||
},
|
||||
"node_modules/probot/node_modules/js-yaml": {
|
||||
"version": "3.14.1",
|
||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz",
|
||||
@@ -19517,8 +19515,7 @@
|
||||
"@ioredis/commands": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@ioredis/commands/-/commands-1.2.0.tgz",
|
||||
"integrity": "sha512-Sx1pU8EM64o2BrqNpEO1CNLtKQwyhuXuqyfH7oGKCk+1a33d2r5saW8zNwm3j6BTExtjrv2BxTgzzkMwts6vGg==",
|
||||
"dev": true
|
||||
"integrity": "sha512-Sx1pU8EM64o2BrqNpEO1CNLtKQwyhuXuqyfH7oGKCk+1a33d2r5saW8zNwm3j6BTExtjrv2BxTgzzkMwts6vGg=="
|
||||
},
|
||||
"@istanbuljs/load-nyc-config": {
|
||||
"version": "1.1.0",
|
||||
@@ -22302,31 +22299,6 @@
|
||||
"msgpackr": "^1.5.2",
|
||||
"semver": "^7.3.2",
|
||||
"uuid": "^8.3.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"denque": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz",
|
||||
"integrity": "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==",
|
||||
"dev": true
|
||||
},
|
||||
"ioredis": {
|
||||
"version": "5.3.2",
|
||||
"resolved": "https://registry.npmjs.org/ioredis/-/ioredis-5.3.2.tgz",
|
||||
"integrity": "sha512-1DKMMzlIHM02eBBVOFQ1+AolGjs6+xEcM4PDL7NqOS6szq7H9jSaEkIUH6/a5Hl241LzW6JLSiAbNvTQjUupUA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@ioredis/commands": "^1.1.1",
|
||||
"cluster-key-slot": "^1.1.0",
|
||||
"debug": "^4.3.4",
|
||||
"denque": "^2.1.0",
|
||||
"lodash.defaults": "^4.2.0",
|
||||
"lodash.isarguments": "^3.1.0",
|
||||
"redis-errors": "^1.2.0",
|
||||
"redis-parser": "^3.0.0",
|
||||
"standard-as-callback": "^2.1.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"bytes": {
|
||||
@@ -23809,21 +23781,26 @@
|
||||
"dev": true
|
||||
},
|
||||
"ioredis": {
|
||||
"version": "4.28.5",
|
||||
"resolved": "https://registry.npmjs.org/ioredis/-/ioredis-4.28.5.tgz",
|
||||
"integrity": "sha512-3GYo0GJtLqgNXj4YhrisLaNNvWSNwSS2wS4OELGfGxH8I69+XfNdnmV1AyN+ZqMh0i7eX+SWjrwFKDBDgfBC1A==",
|
||||
"version": "5.3.2",
|
||||
"resolved": "https://registry.npmjs.org/ioredis/-/ioredis-5.3.2.tgz",
|
||||
"integrity": "sha512-1DKMMzlIHM02eBBVOFQ1+AolGjs6+xEcM4PDL7NqOS6szq7H9jSaEkIUH6/a5Hl241LzW6JLSiAbNvTQjUupUA==",
|
||||
"requires": {
|
||||
"@ioredis/commands": "^1.1.1",
|
||||
"cluster-key-slot": "^1.1.0",
|
||||
"debug": "^4.3.1",
|
||||
"denque": "^1.1.0",
|
||||
"debug": "^4.3.4",
|
||||
"denque": "^2.1.0",
|
||||
"lodash.defaults": "^4.2.0",
|
||||
"lodash.flatten": "^4.4.0",
|
||||
"lodash.isarguments": "^3.1.0",
|
||||
"p-map": "^2.1.0",
|
||||
"redis-commands": "1.7.0",
|
||||
"redis-errors": "^1.2.0",
|
||||
"redis-parser": "^3.0.0",
|
||||
"standard-as-callback": "^2.1.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"denque": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz",
|
||||
"integrity": "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"ip": {
|
||||
@@ -27791,6 +27768,24 @@
|
||||
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-8.6.0.tgz",
|
||||
"integrity": "sha512-IrPdXQsk2BbzvCBGBOTmmSH5SodmqZNt4ERAZDmW4CT+tL8VtvinqywuANaFu4bOMWki16nqf0e4oC0QIaDr/g=="
|
||||
},
|
||||
"ioredis": {
|
||||
"version": "4.28.5",
|
||||
"resolved": "https://registry.npmjs.org/ioredis/-/ioredis-4.28.5.tgz",
|
||||
"integrity": "sha512-3GYo0GJtLqgNXj4YhrisLaNNvWSNwSS2wS4OELGfGxH8I69+XfNdnmV1AyN+ZqMh0i7eX+SWjrwFKDBDgfBC1A==",
|
||||
"requires": {
|
||||
"cluster-key-slot": "^1.1.0",
|
||||
"debug": "^4.3.1",
|
||||
"denque": "^1.1.0",
|
||||
"lodash.defaults": "^4.2.0",
|
||||
"lodash.flatten": "^4.4.0",
|
||||
"lodash.isarguments": "^3.1.0",
|
||||
"p-map": "^2.1.0",
|
||||
"redis-commands": "1.7.0",
|
||||
"redis-errors": "^1.2.0",
|
||||
"redis-parser": "^3.0.0",
|
||||
"standard-as-callback": "^2.1.0"
|
||||
}
|
||||
},
|
||||
"js-yaml": {
|
||||
"version": "3.14.1",
|
||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz",
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
"handlebars": "^4.7.7",
|
||||
"helmet": "^5.1.1",
|
||||
"infisical-node": "^1.2.1",
|
||||
"ioredis": "^5.3.2",
|
||||
"js-yaml": "^4.1.0",
|
||||
"jsonwebtoken": "^9.0.0",
|
||||
"jsrp": "^0.2.4",
|
||||
|
||||
@@ -148,7 +148,12 @@ export const changeMembershipRole = async (req: Request, res: Response) => {
|
||||
const membership = await Membership.findByIdAndUpdate(
|
||||
membershipId,
|
||||
{
|
||||
role
|
||||
$set: {
|
||||
role
|
||||
},
|
||||
$unset: {
|
||||
customRole: 1
|
||||
}
|
||||
},
|
||||
{
|
||||
new: true
|
||||
|
||||
@@ -8,7 +8,7 @@ import {
|
||||
} from "../../models";
|
||||
import { createOrganization as create } from "../../helpers/organization";
|
||||
import { addMembershipsOrg } from "../../helpers/membershipOrg";
|
||||
import { ACCEPTED, OWNER } from "../../variables";
|
||||
import { ACCEPTED, ADMIN } from "../../variables";
|
||||
import { getLicenseServerUrl, getSiteURL } from "../../config";
|
||||
import { licenseServerKeyRequest } from "../../config/request";
|
||||
import { validateRequest } from "../../helpers/validation";
|
||||
@@ -55,7 +55,7 @@ export const createOrganization = async (req: Request, res: Response) => {
|
||||
await addMembershipsOrg({
|
||||
userIds: [req.user._id.toString()],
|
||||
organizationId: organization._id.toString(),
|
||||
roles: [OWNER],
|
||||
roles: [ADMIN],
|
||||
statuses: [ACCEPTED]
|
||||
});
|
||||
|
||||
|
||||
@@ -159,7 +159,12 @@ export const updateOrganizationMembership = async (req: Request, res: Response)
|
||||
const membership = await MembershipOrg.findByIdAndUpdate(
|
||||
membershipId,
|
||||
{
|
||||
role
|
||||
$set: {
|
||||
role
|
||||
},
|
||||
$unset: {
|
||||
customRole: 1
|
||||
}
|
||||
},
|
||||
{
|
||||
new: true
|
||||
|
||||
@@ -55,6 +55,9 @@ export const getSecretsRaw = async (req: Request, res: Response) => {
|
||||
secretPath = getFolderWithPathFromId(folder.nodes, folderId).folderPath;
|
||||
}
|
||||
|
||||
if (!environment || !workspaceId)
|
||||
throw BadRequestError({ message: "Missing environment or workspace id" });
|
||||
|
||||
let permissionCheckFn: (env: string, secPath: string) => boolean; // used to pass as callback function to import secret
|
||||
if (req.user?._id) {
|
||||
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
|
||||
|
||||
@@ -171,18 +171,6 @@ export const getRoles = async (req: Request, res: Response) => {
|
||||
const customRoles = await Role.find({ organization: orgId, isOrgRole, workspace: workspaceId });
|
||||
// as this is shared between org and workspace switch the rule set based on it
|
||||
const roles = [
|
||||
// owner is only in org level role
|
||||
...(isOrgRole
|
||||
? [
|
||||
{
|
||||
_id: "owner",
|
||||
name: "Owner",
|
||||
slug: "owner",
|
||||
description: "Complete administration access over the organization.",
|
||||
permissions: adminPermissions.rules
|
||||
}
|
||||
]
|
||||
: []),
|
||||
{
|
||||
_id: "admin",
|
||||
name: "Admin",
|
||||
@@ -192,7 +180,7 @@ export const getRoles = async (req: Request, res: Response) => {
|
||||
},
|
||||
{
|
||||
_id: "member",
|
||||
name: "Member",
|
||||
name: isOrgRole ? "Member" : "Developer",
|
||||
slug: "member",
|
||||
description: "Non-administrative role in an organization",
|
||||
permissions: isOrgRole ? memberPermissions.rules : memberProjectPermissions.rules
|
||||
|
||||
@@ -1,6 +1,14 @@
|
||||
import { Request, Response } from "express";
|
||||
import { PipelineStage, Types } from "mongoose";
|
||||
import { Folder, Membership, Secret, ServiceTokenData, TFolderSchema, User } from "../../../models";
|
||||
import {
|
||||
Folder,
|
||||
Membership,
|
||||
Secret,
|
||||
ServiceTokenData,
|
||||
TFolderSchema,
|
||||
User,
|
||||
Workspace
|
||||
} from "../../../models";
|
||||
import {
|
||||
ActorType,
|
||||
AuditLog,
|
||||
@@ -41,6 +49,7 @@ import {
|
||||
getUserProjectPermissions
|
||||
} from "../../services/ProjectRoleService";
|
||||
import { ForbiddenError } from "@casl/ability";
|
||||
import { BadRequestError } from "../../../utils/errors";
|
||||
|
||||
/**
|
||||
* Return secret snapshots for workspace with id [workspaceId]
|
||||
@@ -781,7 +790,10 @@ export const addWorkspaceTrustedIp = async (req: Request, res: Response) => {
|
||||
ProjectPermissionSub.IpAllowList
|
||||
);
|
||||
|
||||
const plan = await EELicenseService.getPlan(req.workspace.organization);
|
||||
const workspace = await Workspace.findById(workspaceId);
|
||||
if (!workspace) throw BadRequestError({ message: "Workspace not found" });
|
||||
|
||||
const plan = await EELicenseService.getPlan(workspace.organization);
|
||||
|
||||
if (!plan.ipAllowlisting)
|
||||
return res.status(400).send({
|
||||
@@ -844,7 +856,10 @@ export const updateWorkspaceTrustedIp = async (req: Request, res: Response) => {
|
||||
ProjectPermissionSub.IpAllowList
|
||||
);
|
||||
|
||||
const plan = await EELicenseService.getPlan(req.workspace.organization);
|
||||
const workspace = await Workspace.findById(workspaceId);
|
||||
if (!workspace) throw BadRequestError({ message: "Workspace not found" });
|
||||
|
||||
const plan = await EELicenseService.getPlan(workspace.organization);
|
||||
|
||||
if (!plan.ipAllowlisting)
|
||||
return res.status(400).send({
|
||||
@@ -933,7 +948,10 @@ export const deleteWorkspaceTrustedIp = async (req: Request, res: Response) => {
|
||||
ProjectPermissionSub.IpAllowList
|
||||
);
|
||||
|
||||
const plan = await EELicenseService.getPlan(req.workspace.organization);
|
||||
const workspace = await Workspace.findById(workspaceId);
|
||||
if (!workspace) throw BadRequestError({ message: "Workspace not found" });
|
||||
|
||||
const plan = await EELicenseService.getPlan(workspace.organization);
|
||||
|
||||
if (!plan.ipAllowlisting)
|
||||
return res.status(400).send({
|
||||
|
||||
@@ -158,13 +158,39 @@ const buildMemberPermission = () => {
|
||||
can(ProjectPermissionActions.Create, ProjectPermissionSub.SecretRollback);
|
||||
|
||||
can(ProjectPermissionActions.Read, ProjectPermissionSub.Member);
|
||||
can(ProjectPermissionActions.Read, ProjectPermissionSub.Role);
|
||||
can(ProjectPermissionActions.Create, ProjectPermissionSub.Member);
|
||||
|
||||
can(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations);
|
||||
can(ProjectPermissionActions.Create, ProjectPermissionSub.Integrations);
|
||||
can(ProjectPermissionActions.Edit, ProjectPermissionSub.Integrations);
|
||||
can(ProjectPermissionActions.Delete, ProjectPermissionSub.Integrations);
|
||||
|
||||
can(ProjectPermissionActions.Read, ProjectPermissionSub.Webhooks);
|
||||
can(ProjectPermissionActions.Create, ProjectPermissionSub.Webhooks);
|
||||
can(ProjectPermissionActions.Edit, ProjectPermissionSub.Webhooks);
|
||||
can(ProjectPermissionActions.Delete, ProjectPermissionSub.Webhooks);
|
||||
|
||||
can(ProjectPermissionActions.Read, ProjectPermissionSub.ServiceTokens);
|
||||
can(ProjectPermissionActions.Create, ProjectPermissionSub.ServiceTokens);
|
||||
can(ProjectPermissionActions.Edit, ProjectPermissionSub.ServiceTokens);
|
||||
can(ProjectPermissionActions.Delete, ProjectPermissionSub.ServiceTokens);
|
||||
|
||||
can(ProjectPermissionActions.Read, ProjectPermissionSub.Settings);
|
||||
can(ProjectPermissionActions.Create, ProjectPermissionSub.Settings);
|
||||
can(ProjectPermissionActions.Edit, ProjectPermissionSub.Settings);
|
||||
can(ProjectPermissionActions.Delete, ProjectPermissionSub.Settings);
|
||||
|
||||
can(ProjectPermissionActions.Read, ProjectPermissionSub.Environments);
|
||||
can(ProjectPermissionActions.Create, ProjectPermissionSub.Environments);
|
||||
can(ProjectPermissionActions.Edit, ProjectPermissionSub.Environments);
|
||||
can(ProjectPermissionActions.Delete, ProjectPermissionSub.Environments);
|
||||
|
||||
can(ProjectPermissionActions.Read, ProjectPermissionSub.Tags);
|
||||
can(ProjectPermissionActions.Create, ProjectPermissionSub.Tags);
|
||||
can(ProjectPermissionActions.Edit, ProjectPermissionSub.Tags);
|
||||
can(ProjectPermissionActions.Delete, ProjectPermissionSub.Tags);
|
||||
|
||||
can(ProjectPermissionActions.Read, ProjectPermissionSub.Role);
|
||||
can(ProjectPermissionActions.Read, ProjectPermissionSub.AuditLogs);
|
||||
can(ProjectPermissionActions.Read, ProjectPermissionSub.IpAllowList);
|
||||
|
||||
|
||||
@@ -86,12 +86,17 @@ const buildMemberPermission = () => {
|
||||
can(OrgPermissionActions.Read, OrgPermissionSubjects.Workspace);
|
||||
can(OrgPermissionActions.Create, OrgPermissionSubjects.Workspace);
|
||||
can(OrgPermissionActions.Read, OrgPermissionSubjects.Member);
|
||||
can(OrgPermissionActions.Create, OrgPermissionSubjects.Member);
|
||||
can(OrgPermissionActions.Read, OrgPermissionSubjects.Role);
|
||||
can(OrgPermissionActions.Read, OrgPermissionSubjects.Settings);
|
||||
can(OrgPermissionActions.Read, OrgPermissionSubjects.Billing);
|
||||
can(OrgPermissionActions.Read, OrgPermissionSubjects.Sso);
|
||||
can(OrgPermissionActions.Read, OrgPermissionSubjects.IncidentAccount);
|
||||
|
||||
can(OrgPermissionActions.Read, OrgPermissionSubjects.SecretScanning);
|
||||
can(OrgPermissionActions.Create, OrgPermissionSubjects.SecretScanning);
|
||||
can(OrgPermissionActions.Edit, OrgPermissionSubjects.SecretScanning);
|
||||
can(OrgPermissionActions.Delete, OrgPermissionSubjects.SecretScanning);
|
||||
|
||||
return build({ conditionsMatcher });
|
||||
};
|
||||
@@ -114,8 +119,7 @@ export const getUserOrgPermissions = async (userId: string, orgId: string) => {
|
||||
throw UnauthorizedRequestError({ message: "User doesn't belong to organization" });
|
||||
}
|
||||
|
||||
if (membership.role === "admin" || membership.role === "owner")
|
||||
return { permission: adminPermissions, membership };
|
||||
if (membership.role === "admin") return { permission: adminPermissions, membership };
|
||||
|
||||
if (membership.role === "member") return { permission: memberPermissions, membership };
|
||||
|
||||
|
||||
@@ -160,7 +160,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"
|
||||
"workspace integration +accessCiphertext +accessIV +accessTag +accessExpiresAt +refreshCiphertext +refreshIV +refreshTag +accessIdCiphertext +accessIdIV +accessIdTag metadata"
|
||||
);
|
||||
|
||||
if (!integrationAuth)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { IUser } from "../models";
|
||||
import { createOrganization } from "./organization";
|
||||
import { addMembershipsOrg } from "./membershipOrg";
|
||||
import { ACCEPTED, OWNER } from "../variables";
|
||||
import { ACCEPTED, ADMIN } from "../variables";
|
||||
import { sendMail } from "../helpers/nodemailer";
|
||||
import { TokenService } from "../services";
|
||||
import { TOKEN_EMAIL_CONFIRMATION } from "../variables";
|
||||
@@ -14,10 +14,10 @@ import { TOKEN_EMAIL_CONFIRMATION } from "../variables";
|
||||
* @returns {Boolean} success - whether or not operation was successful
|
||||
*/
|
||||
export const sendEmailVerification = async ({ email }: { email: string }) => {
|
||||
const token = await TokenService.createToken({
|
||||
type: TOKEN_EMAIL_CONFIRMATION,
|
||||
email,
|
||||
});
|
||||
const token = await TokenService.createToken({
|
||||
type: TOKEN_EMAIL_CONFIRMATION,
|
||||
email
|
||||
});
|
||||
|
||||
// send mail
|
||||
await sendMail({
|
||||
@@ -25,8 +25,8 @@ export const sendEmailVerification = async ({ email }: { email: string }) => {
|
||||
subjectLine: "Infisical confirmation code",
|
||||
recipients: [email],
|
||||
substitutions: {
|
||||
code: token,
|
||||
},
|
||||
code: token
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
@@ -36,17 +36,11 @@ export const sendEmailVerification = async ({ email }: { email: string }) => {
|
||||
* @param {String} obj.email - emai
|
||||
* @param {String} obj.code - code that was sent to [email]
|
||||
*/
|
||||
export const checkEmailVerification = async ({
|
||||
email,
|
||||
code,
|
||||
}: {
|
||||
email: string;
|
||||
code: string;
|
||||
}) => {
|
||||
export const checkEmailVerification = async ({ email, code }: { email: string; code: string }) => {
|
||||
await TokenService.validateToken({
|
||||
type: TOKEN_EMAIL_CONFIRMATION,
|
||||
email,
|
||||
token: code,
|
||||
token: code
|
||||
});
|
||||
};
|
||||
|
||||
@@ -58,27 +52,27 @@ export const checkEmailVerification = async ({
|
||||
* @param {IUser} obj.user - user who we are initializing for
|
||||
*/
|
||||
export const initializeDefaultOrg = async ({
|
||||
organizationName,
|
||||
user,
|
||||
organizationName,
|
||||
user
|
||||
}: {
|
||||
organizationName: string;
|
||||
user: IUser;
|
||||
organizationName: string;
|
||||
user: IUser;
|
||||
}) => {
|
||||
try {
|
||||
// create organization with user as owner and initialize a free
|
||||
// subscription
|
||||
const organization = await createOrganization({
|
||||
email: user.email,
|
||||
name: organizationName,
|
||||
});
|
||||
try {
|
||||
// create organization with user as owner and initialize a free
|
||||
// subscription
|
||||
const organization = await createOrganization({
|
||||
email: user.email,
|
||||
name: organizationName
|
||||
});
|
||||
|
||||
await addMembershipsOrg({
|
||||
userIds: [user._id.toString()],
|
||||
organizationId: organization._id.toString(),
|
||||
roles: [OWNER],
|
||||
statuses: [ACCEPTED],
|
||||
});
|
||||
} catch (err) {
|
||||
throw new Error(`Failed to initialize default organization and workspace [err=${err}]`);
|
||||
}
|
||||
await addMembershipsOrg({
|
||||
userIds: [user._id.toString()],
|
||||
organizationId: organization._id.toString(),
|
||||
roles: [ADMIN],
|
||||
statuses: [ACCEPTED]
|
||||
});
|
||||
} catch (err) {
|
||||
throw new Error(`Failed to initialize default organization and workspace [err=${err}]`);
|
||||
}
|
||||
};
|
||||
@@ -120,6 +120,7 @@ const getApps = async ({
|
||||
break;
|
||||
case INTEGRATION_GITLAB:
|
||||
apps = await getAppsGitlab({
|
||||
integrationAuth,
|
||||
accessToken,
|
||||
teamId,
|
||||
});
|
||||
@@ -607,6 +608,12 @@ const getAppsLaravelForge = async ({
|
||||
* @returns {String} apps.name - name of Fly.io apps
|
||||
*/
|
||||
const getAppsFlyio = async ({ accessToken }: { accessToken: string }) => {
|
||||
interface FlyioApp {
|
||||
id: string;
|
||||
name: string;
|
||||
hostname: string;
|
||||
}
|
||||
|
||||
const query = `
|
||||
query($role: String) {
|
||||
apps(type: "container", first: 400, role: $role) {
|
||||
@@ -619,7 +626,7 @@ const getAppsFlyio = async ({ accessToken }: { accessToken: string }) => {
|
||||
}
|
||||
`;
|
||||
|
||||
const res = (
|
||||
const res: FlyioApp[] = (
|
||||
await standardRequest.post(
|
||||
INTEGRATION_FLYIO_API_URL,
|
||||
{
|
||||
@@ -638,8 +645,9 @@ const getAppsFlyio = async ({ accessToken }: { accessToken: string }) => {
|
||||
)
|
||||
).data.data.apps.nodes;
|
||||
|
||||
const apps = res.map((a: any) => ({
|
||||
const apps = res.map((a: FlyioApp) => ({
|
||||
name: a.name,
|
||||
appId: a.id
|
||||
}));
|
||||
|
||||
return apps;
|
||||
@@ -736,12 +744,16 @@ const getAppsTerraformCloud = async ({
|
||||
* @returns {String} apps.name - name of GitLab site
|
||||
*/
|
||||
const getAppsGitlab = async ({
|
||||
integrationAuth,
|
||||
accessToken,
|
||||
teamId,
|
||||
}: {
|
||||
integrationAuth: IIntegrationAuth;
|
||||
accessToken: string;
|
||||
teamId?: string;
|
||||
}) => {
|
||||
const gitLabApiUrl = integrationAuth.url ? `${integrationAuth.url}/api` : INTEGRATION_GITLAB_API_URL;
|
||||
|
||||
const apps: App[] = [];
|
||||
|
||||
let page = 1;
|
||||
@@ -758,7 +770,7 @@ const getAppsGitlab = async ({
|
||||
});
|
||||
|
||||
const { data } = await standardRequest.get(
|
||||
`${INTEGRATION_GITLAB_API_URL}/v4/groups/${teamId}/projects`,
|
||||
`${gitLabApiUrl}/v4/groups/${teamId}/projects`,
|
||||
{
|
||||
params,
|
||||
headers: {
|
||||
@@ -785,7 +797,7 @@ const getAppsGitlab = async ({
|
||||
// case: fetch projects for individual in GitLab
|
||||
|
||||
const { id } = (
|
||||
await standardRequest.get(`${INTEGRATION_GITLAB_API_URL}/v4/user`, {
|
||||
await standardRequest.get(`${gitLabApiUrl}/v4/user`, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${accessToken}`,
|
||||
"Accept-Encoding": "application/json",
|
||||
@@ -800,7 +812,7 @@ const getAppsGitlab = async ({
|
||||
});
|
||||
|
||||
const { data } = await standardRequest.get(
|
||||
`${INTEGRATION_GITLAB_API_URL}/v4/users/${id}/projects`,
|
||||
`${gitLabApiUrl}/v4/users/${id}/projects`,
|
||||
{
|
||||
params,
|
||||
headers: {
|
||||
|
||||
@@ -329,6 +329,10 @@ const syncSecretsGCPSecretManager = async ({
|
||||
let pageToken: string | undefined;
|
||||
let hasMorePages = true;
|
||||
|
||||
const filterParam = integration.metadata.secretGCPLabel
|
||||
? `?filter=labels.${integration.metadata.secretGCPLabel.labelName}=${integration.metadata.secretGCPLabel.labelValue}`
|
||||
: "";
|
||||
|
||||
while (hasMorePages) {
|
||||
const params = new URLSearchParams({
|
||||
pageSize: String(pageSize),
|
||||
@@ -336,7 +340,7 @@ const syncSecretsGCPSecretManager = async ({
|
||||
});
|
||||
|
||||
const res: GCPSMListSecretsRes = (await standardRequest.get(
|
||||
`${INTEGRATION_GCP_SECRET_MANAGER_URL}/v1/projects/${integration.appId}/secrets?filter=labels.managed-by=infisical`,
|
||||
`${INTEGRATION_GCP_SECRET_MANAGER_URL}/v1/projects/${integration.appId}/secrets${filterParam}`,
|
||||
{
|
||||
params,
|
||||
headers: {
|
||||
@@ -347,7 +351,24 @@ const syncSecretsGCPSecretManager = async ({
|
||||
)).data;
|
||||
|
||||
if (res.secrets) {
|
||||
gcpSecrets = gcpSecrets.concat(res.secrets);
|
||||
const filteredSecrets = res.secrets?.filter((gcpSecret) => {
|
||||
const arr = gcpSecret.name.split("/");
|
||||
const key = arr[arr.length - 1];
|
||||
|
||||
let isValid = true;
|
||||
|
||||
if (integration.metadata.secretPrefix && !key.startsWith(integration.metadata.secretPrefix)) {
|
||||
isValid = false;
|
||||
}
|
||||
|
||||
if (integration.metadata.secretSuffix && !key.endsWith(integration.metadata.secretSuffix)) {
|
||||
isValid = false;
|
||||
}
|
||||
|
||||
return isValid;
|
||||
});
|
||||
|
||||
gcpSecrets = gcpSecrets.concat(filteredSecrets);
|
||||
}
|
||||
|
||||
if (!res.nextPageToken) {
|
||||
@@ -371,7 +392,7 @@ const syncSecretsGCPSecretManager = async ({
|
||||
const key = arr[arr.length - 1];
|
||||
|
||||
const secretLatest: GCPLatestSecretVersionAccess = (await standardRequest.get(
|
||||
`${INTEGRATION_GCP_SECRET_MANAGER_URL}/v1beta1/projects/${integration.appId}/secrets/${key}/versions/latest:access`,
|
||||
`${INTEGRATION_GCP_SECRET_MANAGER_URL}/v1/projects/${integration.appId}/secrets/${key}/versions/latest:access`,
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Bearer ${accessToken}`,
|
||||
@@ -380,6 +401,7 @@ const syncSecretsGCPSecretManager = async ({
|
||||
}
|
||||
)).data;
|
||||
|
||||
|
||||
res[key] = Buffer.from(secretLatest.payload.data, "base64").toString("utf-8");
|
||||
}
|
||||
|
||||
@@ -387,14 +409,16 @@ const syncSecretsGCPSecretManager = async ({
|
||||
if (!(key in res)) {
|
||||
// case: create secret
|
||||
await standardRequest.post(
|
||||
`${INTEGRATION_GCP_SECRET_MANAGER_URL}/v1beta1/projects/${integration.appId}/secrets`,
|
||||
`${INTEGRATION_GCP_SECRET_MANAGER_URL}/v1/projects/${integration.appId}/secrets`,
|
||||
{
|
||||
replication: {
|
||||
automatic: {}
|
||||
},
|
||||
labels: {
|
||||
"managed-by": "infisical"
|
||||
}
|
||||
...(integration.metadata.secretGCPLabel ? {
|
||||
labels: {
|
||||
[integration.metadata.secretGCPLabel.labelName]: integration.metadata.secretGCPLabel.labelValue
|
||||
}
|
||||
} : {})
|
||||
},
|
||||
{
|
||||
params: {
|
||||
@@ -408,7 +432,7 @@ const syncSecretsGCPSecretManager = async ({
|
||||
);
|
||||
|
||||
await standardRequest.post(
|
||||
`${INTEGRATION_GCP_SECRET_MANAGER_URL}/v1beta1/projects/${integration.appId}/secrets/${key}:addVersion`,
|
||||
`${INTEGRATION_GCP_SECRET_MANAGER_URL}/v1/projects/${integration.appId}/secrets/${key}:addVersion`,
|
||||
{
|
||||
payload: {
|
||||
data: Buffer.from(secrets[key].value).toString("base64")
|
||||
@@ -428,7 +452,7 @@ const syncSecretsGCPSecretManager = async ({
|
||||
if (!(key in secrets)) {
|
||||
// case: delete secret
|
||||
await standardRequest.delete(
|
||||
`${INTEGRATION_GCP_SECRET_MANAGER_URL}/v1beta1/projects/${integration.appId}/secrets/${key}`,
|
||||
`${INTEGRATION_GCP_SECRET_MANAGER_URL}/v1/projects/${integration.appId}/secrets/${key}`,
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Bearer ${accessToken}`,
|
||||
@@ -440,7 +464,7 @@ const syncSecretsGCPSecretManager = async ({
|
||||
// case: update secret
|
||||
if (secrets[key].value !== res[key]) {
|
||||
await standardRequest.post(
|
||||
`${INTEGRATION_GCP_SECRET_MANAGER_URL}/v1beta1/projects/${integration.appId}/secrets/${key}:addVersion`,
|
||||
`${INTEGRATION_GCP_SECRET_MANAGER_URL}/v1/projects/${integration.appId}/secrets/${key}:addVersion`,
|
||||
{
|
||||
payload: {
|
||||
data: Buffer.from(secrets[key].value).toString("base64")
|
||||
@@ -759,12 +783,12 @@ const syncSecretsAWSParameterStore = async ({
|
||||
};
|
||||
|
||||
/**
|
||||
* Sync/push [secrets] to AWS secret manager
|
||||
* Sync/push [secrets] to AWS Secrets Manager
|
||||
* @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.accessId - access id for AWS secret manager integration
|
||||
* @param {String} obj.accessToken - access token for AWS secret manager integration
|
||||
* @param {String} obj.accessId - access id for AWS Secrets Manager integration
|
||||
* @param {String} obj.accessToken - access token for AWS Secrets Manager integration
|
||||
*/
|
||||
const syncSecretsAWSSecretManager = async ({
|
||||
integration,
|
||||
@@ -1863,9 +1887,23 @@ const syncSecretsGitLab = async ({
|
||||
};
|
||||
|
||||
const allEnvVariables = await getAllEnvVariables(integration?.appId, accessToken);
|
||||
const getSecretsRes: GitLabSecret[] = allEnvVariables.filter(
|
||||
(secret: GitLabSecret) => secret.environment_scope === integration.targetEnvironment
|
||||
);
|
||||
const getSecretsRes: GitLabSecret[] = allEnvVariables
|
||||
.filter(
|
||||
(secret: GitLabSecret) => secret.environment_scope === integration.targetEnvironment
|
||||
)
|
||||
.filter((gitLabSecret) => {
|
||||
let isValid = true;
|
||||
|
||||
if (integration.metadata.secretPrefix && !gitLabSecret.key.startsWith(integration.metadata.secretPrefix)) {
|
||||
isValid = false;
|
||||
}
|
||||
|
||||
if (integration.metadata.secretSuffix && !gitLabSecret.key.endsWith(integration.metadata.secretSuffix)) {
|
||||
isValid = false;
|
||||
}
|
||||
|
||||
return isValid;
|
||||
});
|
||||
|
||||
for await (const key of Object.keys(secrets)) {
|
||||
const existingSecret = getSecretsRes.find((s: any) => s.key == key);
|
||||
|
||||
@@ -34,6 +34,7 @@ const getTeams = async ({
|
||||
switch (integrationAuth.integration) {
|
||||
case INTEGRATION_GITLAB:
|
||||
teams = await getTeamsGitLab({
|
||||
integrationAuth,
|
||||
accessToken,
|
||||
});
|
||||
break;
|
||||
@@ -51,13 +52,17 @@ const getTeams = async ({
|
||||
* @returns {String} teams.teamId - id of team
|
||||
*/
|
||||
const getTeamsGitLab = async ({
|
||||
integrationAuth,
|
||||
accessToken,
|
||||
}: {
|
||||
integrationAuth: IIntegrationAuth;
|
||||
accessToken: string;
|
||||
}) => {
|
||||
const gitLabApiUrl = integrationAuth.url ? `${integrationAuth.url}/api` : INTEGRATION_GITLAB_API_URL;
|
||||
|
||||
let teams: Team[] = [];
|
||||
const res = (await standardRequest.get(
|
||||
`${INTEGRATION_GITLAB_API_URL}/v4/groups`,
|
||||
`${gitLabApiUrl}/v4/groups`,
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Bearer ${accessToken}`,
|
||||
|
||||
@@ -187,7 +187,8 @@ const integrationSchema = new Schema<IIntegration>(
|
||||
default: "/",
|
||||
},
|
||||
metadata: {
|
||||
type: Schema.Types.Mixed
|
||||
type: Schema.Types.Mixed,
|
||||
default: {}
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
@@ -1,3 +1,11 @@
|
||||
|
||||
// TODO: in the future separate metadata
|
||||
// into distinct types by integration
|
||||
export type Metadata = {
|
||||
secretPrefix?: string;
|
||||
secretSuffix?: string;
|
||||
secretGCPLabel?: {
|
||||
labelName: string;
|
||||
labelValue: string;
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Document, Schema, Types, model } from "mongoose";
|
||||
import { ACCEPTED, ADMIN, CUSTOM, INVITED, MEMBER, OWNER } from "../variables";
|
||||
import { ACCEPTED, ADMIN, CUSTOM, INVITED, MEMBER } from "../variables";
|
||||
|
||||
export interface IMembershipOrg extends Document {
|
||||
_id: Types.ObjectId;
|
||||
@@ -26,7 +26,7 @@ const membershipOrgSchema = new Schema(
|
||||
},
|
||||
role: {
|
||||
type: String,
|
||||
enum: [OWNER, ADMIN, MEMBER, CUSTOM],
|
||||
enum: [ADMIN, MEMBER, CUSTOM],
|
||||
required: true
|
||||
},
|
||||
status: {
|
||||
|
||||
@@ -35,9 +35,12 @@ syncSecretsToThirdPartyServices.process(async (job: Job) => {
|
||||
});
|
||||
|
||||
const suffixedSecrets: any = {};
|
||||
if (integration.metadata?.secretSuffix) {
|
||||
if (integration.metadata) {
|
||||
for (const key in secrets) {
|
||||
const newKey = key + integration.metadata?.secretSuffix;
|
||||
const prefix = (integration.metadata?.secretPrefix || "");
|
||||
const suffix = (integration.metadata?.secretSuffix || "");
|
||||
const newKey = prefix + key + suffix;
|
||||
|
||||
suffixedSecrets[newKey] = secrets[key];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import TelemetryService from "../../services/TelemetryService";
|
||||
import { sendMail } from "../../helpers";
|
||||
import GitRisks from "../../ee/models/gitRisks";
|
||||
import { MembershipOrg, User } from "../../models";
|
||||
import { ADMIN, OWNER } from "../../variables";
|
||||
import { ADMIN } from "../../variables";
|
||||
import { convertKeysToLowercase, scanFullRepoContentAndGetFindings } from "../../ee/services/GithubSecretScanning/helper";
|
||||
import { getSecretScanningGitAppId, getSecretScanningPrivateKey } from "../../config";
|
||||
import { SecretMatch } from "../../ee/services/GithubSecretScanning/types";
|
||||
@@ -24,22 +24,22 @@ githubFullRepositorySecretScan.process(async (job: Job, done: Queue.DoneCallback
|
||||
const { organizationId, repository, installationId }: TScanPushEventQueueDetails = job.data
|
||||
try {
|
||||
const octokit = new ProbotOctokit({
|
||||
auth: {
|
||||
auth: {
|
||||
appId: await getSecretScanningGitAppId(),
|
||||
privateKey: await getSecretScanningPrivateKey(),
|
||||
installationId: installationId
|
||||
},
|
||||
},
|
||||
});
|
||||
const findings : SecretMatch[] = await scanFullRepoContentAndGetFindings(octokit, installationId, repository.fullName)
|
||||
const findings: SecretMatch[] = await scanFullRepoContentAndGetFindings(octokit, installationId, repository.fullName)
|
||||
for (const finding of findings) {
|
||||
await GitRisks.findOneAndUpdate({ fingerprint: finding.Fingerprint},
|
||||
await GitRisks.findOneAndUpdate({ fingerprint: finding.Fingerprint },
|
||||
{
|
||||
...convertKeysToLowercase(finding),
|
||||
installationId: installationId,
|
||||
organization: organizationId,
|
||||
repositoryFullName: repository.fullName,
|
||||
repositoryId: repository.id
|
||||
}, {
|
||||
...convertKeysToLowercase(finding),
|
||||
installationId: installationId,
|
||||
organization: organizationId,
|
||||
repositoryFullName: repository.fullName,
|
||||
repositoryId: repository.id
|
||||
}, {
|
||||
upsert: true
|
||||
}).lean()
|
||||
}
|
||||
@@ -47,10 +47,7 @@ githubFullRepositorySecretScan.process(async (job: Job, done: Queue.DoneCallback
|
||||
// get emails of admins
|
||||
const adminsOfWork = await MembershipOrg.find({
|
||||
organization: organizationId,
|
||||
$or: [
|
||||
{ role: OWNER },
|
||||
{ role: ADMIN }
|
||||
]
|
||||
role: ADMIN,
|
||||
}).lean()
|
||||
|
||||
const userEmails = await User.find({
|
||||
|
||||
@@ -5,7 +5,7 @@ import TelemetryService from "../../services/TelemetryService";
|
||||
import { sendMail } from "../../helpers";
|
||||
import GitRisks from "../../ee/models/gitRisks";
|
||||
import { MembershipOrg, User } from "../../models";
|
||||
import { ADMIN, OWNER } from "../../variables";
|
||||
import { ADMIN } from "../../variables";
|
||||
import { convertKeysToLowercase, scanContentAndGetFindings } from "../../ee/services/GithubSecretScanning/helper";
|
||||
import { getSecretScanningGitAppId, getSecretScanningPrivateKey } from "../../config";
|
||||
import { SecretMatch } from "../../ee/services/GithubSecretScanning/types";
|
||||
@@ -88,10 +88,7 @@ githubPushEventSecretScan.process(async (job: Job, done: Queue.DoneCallback) =>
|
||||
// get emails of admins
|
||||
const adminsOfWork = await MembershipOrg.find({
|
||||
organization: organizationId,
|
||||
$or: [
|
||||
{ role: OWNER },
|
||||
{ role: ADMIN }
|
||||
]
|
||||
role: ADMIN
|
||||
}).lean()
|
||||
|
||||
const userEmails = await User.find({
|
||||
|
||||
@@ -4,7 +4,7 @@ import {
|
||||
requireAuth,
|
||||
requireOrganizationAuth
|
||||
} from "../../middleware";
|
||||
import { ACCEPTED, ADMIN, AuthMode, OWNER } from "../../variables";
|
||||
import { ACCEPTED, ADMIN, AuthMode } from "../../variables";
|
||||
import { organizationsController } from "../../controllers/v2";
|
||||
|
||||
// TODO: /POST to create membership
|
||||
@@ -48,7 +48,7 @@ router.get(
|
||||
acceptedAuthModes: [AuthMode.JWT]
|
||||
}),
|
||||
requireOrganizationAuth({
|
||||
acceptedRoles: [OWNER, ADMIN],
|
||||
acceptedRoles: [ADMIN],
|
||||
acceptedStatuses: [ACCEPTED]
|
||||
}),
|
||||
organizationsController.getOrganizationServiceAccounts
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
import Redis, { Redis as TRedis } from "ioredis";
|
||||
|
||||
let redisClient: TRedis | null;
|
||||
|
||||
if (process.env.REDIS_URL) {
|
||||
redisClient = new Redis(process.env.REDIS_URL as string);
|
||||
} else {
|
||||
console.warn("Redis URL not set, skipping Redis initialization.");
|
||||
redisClient = null;
|
||||
}
|
||||
|
||||
export { redisClient };
|
||||
@@ -80,7 +80,7 @@ export const createTestUserForDevelopment = async () => {
|
||||
const testMembershipOrg = {
|
||||
_id: testMembershipOrgId,
|
||||
organization: testOrgId,
|
||||
role: "owner",
|
||||
role: "admin",
|
||||
status: "accepted",
|
||||
user: testUserId,
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ import crypto from "crypto";
|
||||
import { Types } from "mongoose";
|
||||
import { encryptSymmetric128BitHexKeyUTF8 } from "../crypto";
|
||||
import { EESecretService } from "../../ee/services";
|
||||
import { redisClient } from "../../services/RedisService"
|
||||
import { IPType, ISecretVersion, SecretSnapshot, SecretVersion, TrustedIP } from "../../ee/models";
|
||||
import {
|
||||
AuthMethod,
|
||||
@@ -10,9 +11,11 @@ import {
|
||||
Bot,
|
||||
BotOrg,
|
||||
ISecret,
|
||||
IWorkspace,
|
||||
Integration,
|
||||
IntegrationAuth,
|
||||
Membership,
|
||||
MembershipOrg,
|
||||
Organization,
|
||||
Secret,
|
||||
SecretBlindIndexData,
|
||||
@@ -23,13 +26,22 @@ import {
|
||||
import { generateKeyPair } from "../../utils/crypto";
|
||||
import { client, getEncryptionKey, getRootEncryptionKey } from "../../config";
|
||||
import {
|
||||
ADMIN,
|
||||
ALGORITHM_AES_256_GCM,
|
||||
CUSTOM,
|
||||
ENCODING_SCHEME_BASE64,
|
||||
ENCODING_SCHEME_UTF8,
|
||||
MEMBER,
|
||||
VIEWER
|
||||
OWNER
|
||||
} from "../../variables";
|
||||
|
||||
import { InternalServerError } from "../errors";
|
||||
import {
|
||||
ProjectPermissionActions,
|
||||
ProjectPermissionSub,
|
||||
memberProjectPermissions
|
||||
} from "../../ee/services/ProjectRoleService";
|
||||
import Role from "../../ee/models/role";
|
||||
|
||||
/**
|
||||
* Backfill secrets to ensure that they're all versioned and have
|
||||
@@ -675,21 +687,135 @@ export const backfillUserAuthMethods = async () => {
|
||||
};
|
||||
|
||||
export const backfillPermission = async () => {
|
||||
await Membership.updateMany(
|
||||
{
|
||||
deniedPermissions: {
|
||||
$exists: true,
|
||||
$ne: []
|
||||
},
|
||||
role: MEMBER
|
||||
},
|
||||
[
|
||||
{
|
||||
$set: {
|
||||
role: VIEWER
|
||||
const lockKey = "backfill_permission_lock";
|
||||
const timeout = 900000; // 15 min lock timeout in milliseconds
|
||||
const lock = await redisClient?.set(lockKey, 1, "PX", timeout, "NX");
|
||||
|
||||
if (lock) {
|
||||
try {
|
||||
console.info("Lock acquired for script [backfillPermission]");
|
||||
|
||||
const memberships = await Membership.find({
|
||||
deniedPermissions: {
|
||||
$exists: true,
|
||||
$ne: []
|
||||
},
|
||||
role: MEMBER,
|
||||
})
|
||||
.populate<{ workspace: IWorkspace }>("workspace")
|
||||
.lean();
|
||||
|
||||
// group memberships that need the same permission set
|
||||
const roleMap = new Map<string, { membershipIds: string[], permissions: any[], organizationId: string, workspaceId: string }>();
|
||||
|
||||
for (const membership of memberships) {
|
||||
// get permissions of members except secret permission
|
||||
const customPermissions = memberProjectPermissions.rules.filter(
|
||||
({ subject }) => subject !== ProjectPermissionSub.Secrets
|
||||
);
|
||||
const secretAccessRule: Record<string, { read: boolean; write: boolean }> = {};
|
||||
|
||||
// iterate and record true and false ones
|
||||
membership.deniedPermissions.forEach(({ ability, environmentSlug }) => {
|
||||
if (!secretAccessRule?.[environmentSlug])
|
||||
secretAccessRule[environmentSlug] = { read: true, write: true };
|
||||
if (ability === "write") secretAccessRule[environmentSlug].write = false;
|
||||
if (ability === "read") secretAccessRule[environmentSlug].read = false;
|
||||
});
|
||||
|
||||
// environments that are not listed in deniedPermissions should be set to allowed for both read & and write
|
||||
membership.workspace.environments.forEach(env => {
|
||||
if (!secretAccessRule?.[env.slug]) {
|
||||
secretAccessRule[env.slug] = { read: true, write: true };
|
||||
}
|
||||
})
|
||||
|
||||
const secretPermissions: any = [];
|
||||
Object.entries(secretAccessRule).forEach(([envSlug, { read, write }]) => {
|
||||
if (read) {
|
||||
secretPermissions.push({
|
||||
subject: ProjectPermissionSub.Secrets,
|
||||
action: ProjectPermissionActions.Read,
|
||||
conditions: { environment: envSlug }
|
||||
});
|
||||
}
|
||||
if (write) {
|
||||
secretPermissions.push(
|
||||
{
|
||||
subject: ProjectPermissionSub.Secrets,
|
||||
action: ProjectPermissionActions.Edit,
|
||||
conditions: { environment: envSlug }
|
||||
},
|
||||
{
|
||||
subject: ProjectPermissionSub.Secrets,
|
||||
action: ProjectPermissionActions.Delete,
|
||||
conditions: { environment: envSlug }
|
||||
},
|
||||
{
|
||||
subject: ProjectPermissionSub.Secrets,
|
||||
action: ProjectPermissionActions.Create,
|
||||
conditions: { environment: envSlug }
|
||||
}
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
const key = `${JSON.stringify(secretPermissions)}-${membership.workspace._id.toString()}`; // group roles that have same permission with in the same workspace
|
||||
const value = roleMap.get(key);
|
||||
if (value) {
|
||||
value.membershipIds.push(membership._id.toString());
|
||||
value.organizationId = membership.workspace.organization.toString()
|
||||
value.workspaceId = membership.workspace._id.toString()
|
||||
} else {
|
||||
roleMap.set(key, { membershipIds: [membership._id.toString()], permissions: [...customPermissions, ...secretPermissions], organizationId: membership.workspace.organization.toString(), workspaceId: membership.workspace._id.toString() });
|
||||
}
|
||||
}
|
||||
]
|
||||
);
|
||||
console.log("Backfill: Finishing converting old denied permission in workspace to viewers");
|
||||
|
||||
for (const [key, value] of roleMap.entries()) {
|
||||
const { membershipIds, permissions, workspaceId, organizationId } = value
|
||||
const membership_identity = crypto.randomBytes(3).toString("hex")
|
||||
const role = new Role({
|
||||
name: `Limited [${membership_identity.toUpperCase()}]`,
|
||||
organization: organizationId,
|
||||
workspace: workspaceId,
|
||||
description: "This role was auto generated by Infisical in effort to migrate your project members to our new permission system",
|
||||
isOrgRole: false,
|
||||
slug: `custom-role-${membership_identity}`,
|
||||
permissions: permissions
|
||||
});
|
||||
|
||||
await role.save();
|
||||
|
||||
for (const id of membershipIds) {
|
||||
await Membership.findByIdAndUpdate(id, { // document db doesn't support update many so we must loop
|
||||
$set: {
|
||||
role: CUSTOM,
|
||||
customRole: role
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
console.info("Backfill: Finished converting old denied permission in workspace to viewers");
|
||||
|
||||
await MembershipOrg.updateMany(
|
||||
{
|
||||
role: OWNER
|
||||
},
|
||||
{
|
||||
$set: {
|
||||
role: ADMIN
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
console.info("Backfill: Finished converting owner role to member");
|
||||
|
||||
} catch (error) {
|
||||
console.error("An error occurred when running script [backfillPermission]:", error);
|
||||
}
|
||||
|
||||
} else {
|
||||
console.info("Could not acquire lock for script [backfillPermission], skipping");
|
||||
}
|
||||
};
|
||||
|
||||
@@ -84,7 +84,7 @@ export const setup = async () => {
|
||||
await backfillServiceTokenMultiScope();
|
||||
await backfillTrustedIps();
|
||||
await backfillUserAuthMethods();
|
||||
await backfillPermission();
|
||||
// await backfillPermission();
|
||||
|
||||
// re-encrypt any data previously encrypted under server hex 128-bit ENCRYPTION_KEY
|
||||
// to base64 256-bit ROOT_ENCRYPTION_KEY
|
||||
|
||||
@@ -34,7 +34,7 @@ export const validateClientForIntegration = async ({
|
||||
if (!integration) throw IntegrationNotFoundError();
|
||||
|
||||
const integrationAuth = await IntegrationAuth.findById(integration.integrationAuth).select(
|
||||
"+refreshCiphertext +refreshIV +refreshTag +accessCiphertext +accessIV +accessTag +accessExpiresAt"
|
||||
"+refreshCiphertext +refreshIV +refreshTag +accessCiphertext +accessIV +accessTag +accessExpiresAt metadata"
|
||||
);
|
||||
|
||||
if (!integrationAuth) throw IntegrationAuthNotFoundError();
|
||||
@@ -77,7 +77,12 @@ export const CreateIntegrationV1 = z.object({
|
||||
path: z.string().trim().optional(),
|
||||
region: z.string().trim().optional(),
|
||||
metadata: z.object({
|
||||
secretSuffix: z.string().optional()
|
||||
secretPrefix: z.string().optional(),
|
||||
secretSuffix: z.string().optional(),
|
||||
secretGCPLabel: z.object({
|
||||
labelName: z.string(),
|
||||
labelValue: z.string()
|
||||
}).optional()
|
||||
}).optional()
|
||||
})
|
||||
});
|
||||
|
||||
@@ -30,7 +30,7 @@ const validateClientForIntegrationAuth = async ({
|
||||
const integrationAuth = await IntegrationAuth.findById(integrationAuthId)
|
||||
.populate<{ workspace: IWorkspace }>("workspace")
|
||||
.select(
|
||||
"+refreshCiphertext +refreshIV +refreshTag +accessCiphertext +accessIV +accessTag +accessExpiresAt"
|
||||
"+refreshCiphertext +refreshIV +refreshTag +accessCiphertext +accessIV +accessTag +accessExpiresAt metadata"
|
||||
);
|
||||
|
||||
if (!integrationAuth) throw IntegrationAuthNotFoundError();
|
||||
|
||||
@@ -225,8 +225,8 @@ export const GetSecretsV2 = z.object({
|
||||
|
||||
export const GetSecretsRawV3 = z.object({
|
||||
query: z.object({
|
||||
workspaceId: z.string().trim(),
|
||||
environment: z.string().trim(),
|
||||
workspaceId: z.string().trim().optional(),
|
||||
environment: z.string().trim().optional(),
|
||||
secretPath: z.string().trim().default("/"),
|
||||
folderId: z.string().trim().optional(),
|
||||
include_imports: z
|
||||
@@ -270,7 +270,6 @@ export const UpdateSecretByNameRawV3 = z.object({
|
||||
workspaceId: z.string().trim(),
|
||||
environment: z.string().trim(),
|
||||
secretValue: z.string().trim(),
|
||||
secretName: z.string().trim(),
|
||||
secretPath: z.string().trim().default("/"),
|
||||
type: z.enum([SECRET_SHARED, SECRET_PERSONAL]).default(SECRET_SHARED)
|
||||
})
|
||||
|
||||
@@ -192,7 +192,7 @@ export const getIntegrationOptions = async () => {
|
||||
docsLink: "",
|
||||
},
|
||||
{
|
||||
name: "AWS Secret Manager",
|
||||
name: "AWS Secrets Manager",
|
||||
slug: "aws-secret-manager",
|
||||
image: "Amazon Web Services.png",
|
||||
isAvailable: true,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// membership roles
|
||||
export const OWNER = "owner";
|
||||
export const OWNER = "owner"; // depreciated
|
||||
export const ADMIN = "admin";
|
||||
export const MEMBER = "member";
|
||||
export const VIEWER = "viewer";
|
||||
|
||||
@@ -402,7 +402,12 @@ func getSecretsByNames(cmd *cobra.Command, args []string) {
|
||||
util.HandleError(err, "Unable to parse flag")
|
||||
}
|
||||
|
||||
secrets, err := util.GetAllEnvironmentVariables(models.GetAllSecretsParameters{Environment: environmentName, InfisicalToken: infisicalToken, TagSlugs: tagSlugs})
|
||||
secretsPath, err := cmd.Flags().GetString("path")
|
||||
if err != nil {
|
||||
util.HandleError(err, "Unable to parse path flag")
|
||||
}
|
||||
|
||||
secrets, err := util.GetAllEnvironmentVariables(models.GetAllSecretsParameters{Environment: environmentName, InfisicalToken: infisicalToken, TagSlugs: tagSlugs, SecretsPath: secretsPath})
|
||||
if err != nil {
|
||||
util.HandleError(err, "To fetch all secrets")
|
||||
}
|
||||
@@ -651,10 +656,11 @@ func init() {
|
||||
|
||||
secretsGetCmd.Flags().String("token", "", "Fetch secrets using the Infisical Token")
|
||||
secretsCmd.AddCommand(secretsGetCmd)
|
||||
secretsGetCmd.Flags().String("path", "/", "get secrets within a folder path")
|
||||
|
||||
secretsCmd.Flags().Bool("secret-overriding", true, "Prioritizes personal secrets, if any, with the same name over shared secrets")
|
||||
secretsCmd.AddCommand(secretsSetCmd)
|
||||
secretsSetCmd.Flags().String("path", "/", "get secrets within a folder path")
|
||||
secretsSetCmd.Flags().String("path", "/", "set secrets within a folder path")
|
||||
|
||||
secretsSetCmd.PersistentPreRun = func(cmd *cobra.Command, args []string) {
|
||||
util.RequireLogin()
|
||||
|
||||
@@ -220,11 +220,11 @@ func GetAllEnvironmentVariables(params models.GetAllSecretsParameters) ([]models
|
||||
workspaceFile.WorkspaceId = params.WorkspaceId
|
||||
}
|
||||
|
||||
// Verify environment
|
||||
err = ValidateEnvironmentName(params.Environment, workspaceFile.WorkspaceId, loggedInUserDetails.UserCredentials)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to validate environment name because [err=%s]", err)
|
||||
}
|
||||
// // Verify environment
|
||||
// err = ValidateEnvironmentName(params.Environment, workspaceFile.WorkspaceId, loggedInUserDetails.UserCredentials)
|
||||
// if err != nil {
|
||||
// return nil, fmt.Errorf("unable to validate environment name because [err=%s]", err)
|
||||
// }
|
||||
|
||||
secretsToReturn, errorToReturn = GetPlainTextSecretsViaJTW(loggedInUserDetails.UserCredentials.JTWToken, loggedInUserDetails.UserCredentials.PrivateKey, workspaceFile.WorkspaceId,
|
||||
params.Environment, params.TagSlugs, params.SecretsPath, params.IncludeImport)
|
||||
@@ -253,32 +253,32 @@ func GetAllEnvironmentVariables(params models.GetAllSecretsParameters) ([]models
|
||||
return secretsToReturn, errorToReturn
|
||||
}
|
||||
|
||||
func ValidateEnvironmentName(environmentName string, workspaceId string, userLoggedInDetails models.UserCredentials) error {
|
||||
httpClient := resty.New()
|
||||
httpClient.SetAuthToken(userLoggedInDetails.JTWToken).
|
||||
SetHeader("Accept", "application/json")
|
||||
// func ValidateEnvironmentName(environmentName string, workspaceId string, userLoggedInDetails models.UserCredentials) error {
|
||||
// httpClient := resty.New()
|
||||
// httpClient.SetAuthToken(userLoggedInDetails.JTWToken).
|
||||
// SetHeader("Accept", "application/json")
|
||||
|
||||
response, err := api.CallGetAccessibleEnvironments(httpClient, api.GetAccessibleEnvironmentsRequest{WorkspaceId: workspaceId})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// response, err := api.CallGetAccessibleEnvironments(httpClient, api.GetAccessibleEnvironmentsRequest{WorkspaceId: workspaceId})
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
|
||||
listOfEnvSlugs := []string{}
|
||||
mapOfEnvSlugs := make(map[string]interface{})
|
||||
// listOfEnvSlugs := []string{}
|
||||
// mapOfEnvSlugs := make(map[string]interface{})
|
||||
|
||||
for _, environment := range response.AccessibleEnvironments {
|
||||
listOfEnvSlugs = append(listOfEnvSlugs, environment.Slug)
|
||||
mapOfEnvSlugs[environment.Slug] = environment
|
||||
}
|
||||
// for _, environment := range response.AccessibleEnvironments {
|
||||
// listOfEnvSlugs = append(listOfEnvSlugs, environment.Slug)
|
||||
// mapOfEnvSlugs[environment.Slug] = environment
|
||||
// }
|
||||
|
||||
_, exists := mapOfEnvSlugs[environmentName]
|
||||
if !exists {
|
||||
HandleError(fmt.Errorf("the environment [%s] does not exist in project with [id=%s]. Only [%s] are available", environmentName, workspaceId, strings.Join(listOfEnvSlugs, ",")))
|
||||
}
|
||||
// _, exists := mapOfEnvSlugs[environmentName]
|
||||
// if !exists {
|
||||
// HandleError(fmt.Errorf("the environment [%s] does not exist in project with [id=%s]. Only [%s] are available", environmentName, workspaceId, strings.Join(listOfEnvSlugs, ",")))
|
||||
// }
|
||||
|
||||
return nil
|
||||
// return nil
|
||||
|
||||
}
|
||||
// }
|
||||
|
||||
func getExpandedEnvVariable(secrets []models.SingleEnvironmentVariable, variableWeAreLookingFor string, hashMapOfCompleteVariables map[string]string, hashMapOfSelfRefs map[string]string) string {
|
||||
if value, found := hashMapOfCompleteVariables[variableWeAreLookingFor]; found {
|
||||
|
||||
@@ -76,7 +76,7 @@ The changelog below reflects new product developments and updates on a monthly b
|
||||
- Added secret backups support for the CLI; it now fetches and caches secrets locally to be used in the event of future failed fetch.
|
||||
- Added support for comparing secret values across environments on each secret.
|
||||
- Added native AWS Parameter Store integration.
|
||||
- Added native AWS Secret Manager integration.
|
||||
- Added native AWS Secrets Manager integration.
|
||||
- Added native GitLab integration.
|
||||
- Added native CircleCI integration.
|
||||
- Added native Travis CI integration.
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
---
|
||||
title: "FAQ"
|
||||
description: "Frequently Asked Questions about contributing to Infisical"
|
||||
---
|
||||
|
||||
Frequently asked questions about contributing to Infisical can be found on this page.
|
||||
If you can't find the answer you are looking for, please create an issue on our GitHub repository or join our Slack channel for additional support.
|
||||
|
||||
<Accordion title="Error building backend (Alpine Linux CDN temporary error)">
|
||||
The Alpine Linux CDN may be unavailable/down in your region infrequently (eg. there is an unplanned outage). One possible fix is to add a retry mechanism and a fallback mirrors array to the Dockerfile. You can also use this as an opportunity to pin the Alpine Linux version for Docker to use in case there are issues with the latest version. Ensure to use https for the mirrors.
|
||||
|
||||
#### Make the following changes to the backend Dockerfile
|
||||
```bash
|
||||
# Pin Alpine version from list: https://dl-cdn.alpinelinux.org/alpine/
|
||||
ARG ALPINE_VERSION=3.17
|
||||
ARG ALPINE_APPEND=v3.17/main
|
||||
|
||||
# Specify number of retries for each mirror
|
||||
ARG MAX_RETRIES=3
|
||||
|
||||
# Define base Alpine mirror URLs in attempt order from list: https://dl-cdn.alpinelinux.org/alpine/MIRRORS.txt
|
||||
ARG BASE_ALPINE_MIRRORS="https://dl-cdn.alpinelinux.org/alpine https://ftp.halifax.rwth-aachen.de/alpine https://uk.alpinelinux.org/alpine"
|
||||
|
||||
# Build stage
|
||||
# Add the Alpine version arg
|
||||
FROM node:16-alpine$ALPINE_VERSION AS build
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY package*.json ./
|
||||
RUN npm ci --only-production
|
||||
|
||||
COPY . .
|
||||
RUN npm run build
|
||||
|
||||
# Production stage
|
||||
# Add the Alpine version arg
|
||||
FROM node:16-alpine$ALPINE_VERSION
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
ENV npm_config_cache /home/node/.npm
|
||||
|
||||
COPY package*.json ./
|
||||
RUN npm ci --only-production
|
||||
|
||||
COPY --from=build /app .
|
||||
|
||||
# Add retry mechanism and loop through the specified mirrors
|
||||
RUN retries_left=$MAX_RETRIES; \
|
||||
for mirror in $ALPINE_MIRRORS; do \
|
||||
full_mirror="$mirror/$ALPINE_APPEND"; \
|
||||
echo "Trying mirror: $full_mirror"; \
|
||||
echo >>/etc/apk/repositories "$full_mirror"; \
|
||||
for i in $(seq $retries_left); do \
|
||||
echo "Retrying... Attempt $i (Retries Left: $((retries_left - i)))"; \
|
||||
if apk add --no-cache bash curl git && \
|
||||
curl -1sLf 'https://dl.cloudsmith.io/public/infisical/infisical-cli/setup.alpine.sh' | bash && \
|
||||
apk add --no-cache infisical=0.8.1; then \
|
||||
break; \
|
||||
fi; \
|
||||
sleep 10; \
|
||||
done; \
|
||||
if [ $? -eq 0 ]; then \
|
||||
break; \
|
||||
fi; \
|
||||
done
|
||||
|
||||
HEALTHCHECK --interval=10s --timeout=3s --start-period=10s \
|
||||
CMD node healthcheck.js
|
||||
|
||||
EXPOSE 4000
|
||||
|
||||
CMD ["npm", "run", "start"]
|
||||
```
|
||||
|
||||
<Info>
|
||||
[Alpine Linux (mirrors) - official site](https://dl-cdn.alpinelinux.org/alpine/MIRRORS.txt)
|
||||
</Info>
|
||||
|
||||
<Info>
|
||||
[Alpine Linux (mirrors) - archived site](https://web.archive.org/web/20230914123159/https://dl-cdn.alpinelinux.org/alpine/MIRRORS.txt)
|
||||
</Info>
|
||||
|
||||
<Info>
|
||||
[Alpine Linux (versions) - official site](https://dl-cdn.alpinelinux.org/alpine/)
|
||||
</Info>
|
||||
|
||||
<Info>
|
||||
[Alpine Linux (versions) - archived site](https://web.archive.org/web/20230914123455/https://dl-cdn.alpinelinux.org/alpine/)
|
||||
</Info>
|
||||
|
||||
</Accordion>
|
||||
@@ -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>
|
||||
|
||||
BIN
Binary file not shown.
|
After Width: | Height: | Size: 1.3 MiB |
BIN
Binary file not shown.
|
Before Width: | Height: | Size: 1.2 MiB After Width: | Height: | Size: 1.2 MiB |
Binary file not shown.
|
After Width: | Height: | Size: 1.2 MiB |
Binary file not shown.
|
Before Width: | Height: | Size: 185 KiB After Width: | Height: | Size: 1.3 MiB |
@@ -32,6 +32,16 @@ Press on the GitLab tile and grant Infisical access to your GitLab account.
|
||||
Select which Infisical environment secrets you want to sync to which GitLab repository and press create integration to start syncing secrets to GitLab.
|
||||
|
||||

|
||||
|
||||
Note that the GitLab integration supports a few options in the **Options** tab:
|
||||
|
||||
- Secret Prefix: If inputted, the prefix is appended to the front of every secret name prior to being synced.
|
||||
- Secret Suffix: If inputted, the suffix to appended to the back of every name of every secret prior to being synced.
|
||||
|
||||
Setting a secret prefix or suffix ensures that existing secrets in GCP Secret Manager are not overwritten during the sync. As part of this process, Infisical abstains from mutating any secrets in GitLab without the specified prefix or suffix.
|
||||
|
||||

|
||||
|
||||

|
||||
</Accordion>
|
||||
<Accordion title="Pipeline">
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
title: "AWS Secret Manager"
|
||||
description: "How to sync secrets from Infisical to AWS Secret Manager"
|
||||
title: "AWS Secrets Manager"
|
||||
description: "How to sync secrets from Infisical to AWS Secrets Manager"
|
||||
---
|
||||
|
||||
Prerequisites:
|
||||
@@ -8,15 +8,15 @@ Prerequisites:
|
||||
- Set up and add envars to [Infisical Cloud](https://app.infisical.com)
|
||||
- Set up AWS and have/create an IAM user
|
||||
|
||||
## Grant the IAM user permissions to access AWS Secret Manager
|
||||
## Grant the IAM user permissions to access AWS Secrets Manager
|
||||
|
||||
Navigate to your IAM user permissions and add a permission policy to grant access to AWS Secret Manager.
|
||||
Navigate to your IAM user permissions and add a permission policy to grant access to AWS Secrets Manager.
|
||||
|
||||

|
||||

|
||||

|
||||
|
||||
For better security, here's a custom policy containing the minimum permissions required by Infisical to sync secrets to AWS Secret Manager for the IAM user that you can use:
|
||||
For better security, here's a custom policy containing the minimum permissions required by Infisical to sync secrets to AWS Secrets Manager for the IAM user that you can use:
|
||||
|
||||
```json
|
||||
{
|
||||
@@ -40,7 +40,7 @@ For better security, here's a custom policy containing the minimum permissions r
|
||||
|
||||

|
||||
|
||||
## Authorize Infisical for AWS Secret Manager
|
||||
## Authorize Infisical for AWS Secrets Manager
|
||||
|
||||
Obtain a AWS access key ID and secret access key for your IAM user in IAM > Users > User > Security credentials > Access keys
|
||||
|
||||
@@ -48,7 +48,7 @@ Obtain a AWS access key ID and secret access key for your IAM user in IAM > User
|
||||

|
||||

|
||||
|
||||
Press on the AWS Secret Manager tile and input your AWS access key ID and secret access key from the previous step.
|
||||
Press on the AWS Secrets Manager tile and input your AWS access key ID and secret access key from the previous step.
|
||||
|
||||

|
||||
|
||||
@@ -61,12 +61,12 @@ Press on the AWS Secret Manager tile and input your AWS access key ID and secret
|
||||
|
||||
## Start integration
|
||||
|
||||
Select which Infisical environment secrets you want to sync to which AWS Secret Manager region and under which secret name. Then, press create integration to start syncing secrets to AWS Secret Manager.
|
||||
Select which Infisical environment secrets you want to sync to which AWS Secrets Manager region and under which secret name. Then, press create integration to start syncing secrets to AWS Secrets Manager.
|
||||
|
||||

|
||||
|
||||
<Info>
|
||||
Infisical currently syncs environment variables to AWS Secret Manager as
|
||||
Infisical currently syncs environment variables to AWS Secrets Manager as
|
||||
key-value pairs under one secret. We're actively exploring ways to help users
|
||||
group environment variable key-pairs under multiple secrets for greater
|
||||
control.
|
||||
|
||||
@@ -35,14 +35,21 @@ Grant Infisical access to GCP.
|
||||
|
||||
## Start integration
|
||||
|
||||
Select which Infisical environment secrets you want to sync to which GCP secret manager project. Lastly, press create integration to start syncing secrets to GCP secret manager.
|
||||
In the **Connection** tab, select which Infisical environment secrets you want to sync to which GCP secret manager project. Lastly, press create integration to start syncing secrets to GCP secret manager.
|
||||
|
||||

|
||||

|
||||
|
||||
<Note>
|
||||
Secrets synced from Infisical to GCP Secret Manager are automatically labeled `managed-by:infisical` to avoid overwriting existing values in GCP Secret Manager.
|
||||
</Note>
|
||||
Note that the GCP Secret Manager integration supports a few options in the **Options** tab:
|
||||
|
||||
- Secret Prefix: If inputted, the prefix is appended to the front of every secret name prior to being synced.
|
||||
- Secret Suffix: If inputted, the suffix to appended to the back of every name of every secret prior to being synced.
|
||||
- Label in GCP Secret Manager: If selected, every secret will be labeled in GCP Secret Manager (e.g. as `managed-by:infisical`); labels can be customized.
|
||||
|
||||
Setting a secret prefix, suffix, or enabling the labeling option ensures that existing secrets in GCP Secret Manager are not overwritten during the sync. As part of this process, Infisical abstains from mutating any secrets in GCP Secret Manager without the specified prefix, suffix, or attached label.
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
<Warning>
|
||||
Using Infisical to sync secrets to GCP Secret Manager requires that you enable
|
||||
@@ -89,14 +96,21 @@ service account in IAM & Admin > Service Accounts > Service Account > Keys).
|
||||
|
||||
## Start integration
|
||||
|
||||
Select which Infisical environment secrets you want to sync to the GCP secret manager project. Lastly, press create integration to start syncing secrets to GCP secret manager.
|
||||
In the **Connection** tab, select which Infisical environment secrets you want to sync to the GCP secret manager project. Lastly, press create integration to start syncing secrets to GCP secret manager.
|
||||
|
||||

|
||||

|
||||
|
||||
<Note>
|
||||
Secrets synced from Infisical to GCP Secret Manager are automatically labeled `managed-by:infisical` to avoid overwriting existing values in GCP Secret Manager.
|
||||
</Note>
|
||||
Note that the GCP Secret Manager integration supports a few options in the **Options** tab:
|
||||
|
||||
- Secret Prefix: If inputted, the prefix is appended to the front of every secret name prior to being synced.
|
||||
- Secret Suffix: If inputted, the suffix to appended to the back of every name of every secret prior to being synced.
|
||||
- Label in GCP Secret Manager: If selected, every secret will be labeled in GCP Secret Manager (e.g. as `managed-by:infisical`); labels can be customized.
|
||||
|
||||
Setting a secret prefix, suffix, or enabling the labeling option ensures that existing secrets in GCP Secret Manager are not overwritten during the sync. As part of this process, Infisical abstains from mutating any secrets in GCP Secret Manager without the specified prefix, suffix, or attached label.
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
<Warning>
|
||||
Using Infisical to sync secrets to GCP Secret Manager requires that you enable
|
||||
|
||||
@@ -13,7 +13,7 @@ Prerequisites:
|
||||
|
||||
## Enter your Windmill Access Token
|
||||
|
||||
Obtain a Windmill access token in Access Tokens
|
||||
Obtain a [Windmill](https://www.windmill.dev/) access token in Access Tokens
|
||||
|
||||

|
||||

|
||||
|
||||
@@ -29,7 +29,7 @@ Missing an integration? [Throw in a request](https://github.com/Infisical/infisi
|
||||
| [Checkly](/integrations/cloud/checkly) | Cloud | Available |
|
||||
| [HashiCorp Vault](/integrations/cloud/hashicorp-vault) | Cloud | Available |
|
||||
| [AWS Parameter Store](/integrations/cloud/aws-parameter-store) | Cloud | Available |
|
||||
| [AWS Secret Manager](/integrations/cloud/aws-secret-manager) | Cloud | Available |
|
||||
| [AWS Secrets Manager](/integrations/cloud/aws-secret-manager) | Cloud | Available |
|
||||
| [Azure Key Vault](/integrations/cloud/azure-key-vault) | Cloud | Available |
|
||||
| [GCP Secret Manager](/integrations/cloud/gcp-secret-manager) | Cloud | Available |
|
||||
| [Windmill](/integrations/cloud/windmill) | Cloud | Available |
|
||||
|
||||
+2
-1
@@ -396,7 +396,8 @@
|
||||
"contributing/overview",
|
||||
"contributing/code-of-conduct",
|
||||
"contributing/developing",
|
||||
"contributing/pull-requests"
|
||||
"contributing/pull-requests",
|
||||
"contributing/faq"
|
||||
]
|
||||
}
|
||||
],
|
||||
|
||||
@@ -5,7 +5,7 @@ interface Mapping {
|
||||
const integrationSlugNameMapping: Mapping = {
|
||||
"azure-key-vault": "Azure Key Vault",
|
||||
"aws-parameter-store": "AWS Parameter Store",
|
||||
"aws-secret-manager": "AWS Secret Manager",
|
||||
"aws-secret-manager": "AWS Secrets Manager",
|
||||
"heroku": "Heroku",
|
||||
"vercel": "Vercel",
|
||||
"netlify": "Netlify",
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
const LINE =
|
||||
/(?:^|^)\s*(?:export\s+)?([\w.-:]+)(?:\s*=\s*?|:\s+?)(\s*'(?:\\'|[^'])*'|\s*"(?:\\"|[^"])*"|\s*`(?:\\`|[^`])*`|[^#\r\n]+)?\s*(?:#.*)?(?:$|$)/gm;
|
||||
/(?:^|^)\s*(?:export\s+)?([\w.:-]+)(?:\s*=\s*?|:\s+?)(\s*'(?:\\'|[^'])*'|\s*"(?:\\"|[^"])*"|\s*`(?:\\`|[^`])*`|[^#\r\n]+)?\s*(?:#.*)?(?:$|$)/gm;
|
||||
|
||||
/**
|
||||
* Return text that is the buffer parsed
|
||||
|
||||
@@ -16,14 +16,6 @@ export const ProjectPermissionProvider = ({ children }: Props): JSX.Element => {
|
||||
const workspaceId = currentWorkspace?._id || "";
|
||||
const { data: permission, isLoading } = useGetUserProjectPermissions({ workspaceId });
|
||||
|
||||
if (!permission && currentWorkspace) {
|
||||
return (
|
||||
<div className="flex items-center justify-center w-screen h-screen bg-bunker-800">
|
||||
Failed to load user permissions
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if ((isLoading && currentWorkspace) || isWsLoading) {
|
||||
return (
|
||||
<div className="flex items-center justify-center w-screen h-screen bg-bunker-800">
|
||||
@@ -37,6 +29,14 @@ export const ProjectPermissionProvider = ({ children }: Props): JSX.Element => {
|
||||
);
|
||||
}
|
||||
|
||||
if (!permission && currentWorkspace) {
|
||||
return (
|
||||
<div className="flex items-center justify-center w-screen h-screen bg-bunker-800">
|
||||
Failed to load user permissions
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<ProjectPermissionContext.Provider value={permission!}>
|
||||
{children}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -57,6 +57,7 @@ export const useCreateIntegration = () => {
|
||||
path?: string;
|
||||
region?: string;
|
||||
metadata?: {
|
||||
secretPrefix?: string;
|
||||
secretSuffix?: string;
|
||||
}
|
||||
}) => {
|
||||
|
||||
@@ -138,7 +138,6 @@ export const AppLayout = ({ children }: LayoutProps) => {
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
|
||||
const logout = useLogoutUser();
|
||||
const logOutUser = async () => {
|
||||
try {
|
||||
@@ -446,7 +445,7 @@ export const AppLayout = ({ children }: LayoutProps) => {
|
||||
<a>
|
||||
<MenuItem
|
||||
isSelected={router.asPath.includes(
|
||||
`/project/${currentWorkspace?._id}/secrets/overview`
|
||||
`/project/${currentWorkspace?._id}/secrets`
|
||||
)}
|
||||
icon="system-outline-90-lock-closed"
|
||||
>
|
||||
@@ -469,7 +468,7 @@ export const AppLayout = ({ children }: LayoutProps) => {
|
||||
<Link href={`/integrations/${currentWorkspace?._id}`} passHref>
|
||||
<a>
|
||||
<MenuItem
|
||||
isSelected={router.asPath === `/integrations/${currentWorkspace?._id}`}
|
||||
isSelected={router.asPath.includes("/integrations")}
|
||||
icon="system-outline-82-extension"
|
||||
>
|
||||
{t("nav.menu.integrations")}
|
||||
@@ -488,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={
|
||||
@@ -499,30 +498,7 @@ export const AppLayout = ({ children }: LayoutProps) => {
|
||||
Audit Logs
|
||||
</MenuItem>
|
||||
</a>
|
||||
</Link>
|
||||
{/* <Link href={`/project/${currentWorkspace?._id}/logs`} passHref>
|
||||
<a>
|
||||
<MenuItem
|
||||
isSelected={
|
||||
router.asPath === `/project/${currentWorkspace?._id}/logs`
|
||||
}
|
||||
icon="system-outline-168-view-headline"
|
||||
>
|
||||
Audit Logs
|
||||
</MenuItem>
|
||||
</a>
|
||||
</Link> */}
|
||||
{/* <Link href={`/project/${currentWorkspace?._id}/secret-scanning`} passHref>
|
||||
<a>
|
||||
<MenuItem
|
||||
isSelected={router.asPath === `/project/${currentWorkspace?._id}/secret-scanning`}
|
||||
// icon={<FontAwesomeIcon icon={faFileLines} size="lg" />}
|
||||
icon="system-outline-82-extension"
|
||||
>
|
||||
Audit Logs
|
||||
</MenuItem>
|
||||
</a>
|
||||
</Link> */}
|
||||
<Link href={`/project/${currentWorkspace?._id}/settings`} passHref>
|
||||
<a>
|
||||
<MenuItem
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
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
|
||||
@@ -56,12 +61,41 @@ export default function AWSParameterStoreAuthorizeIntegrationPage() {
|
||||
|
||||
return (
|
||||
<div className="flex h-full w-full items-center justify-center">
|
||||
<Card className="max-w-md rounded-md p-8">
|
||||
<CardTitle className="mb-4 text-center">AWS Parameter Store Integration</CardTitle>
|
||||
<Head>
|
||||
<title>Authorize AWS Parameter Integration</title>
|
||||
<link rel='icon' href='/infisical.ico' />
|
||||
</Head>
|
||||
<Card className="max-w-lg rounded-md border border-mineshaft-600">
|
||||
<CardTitle
|
||||
className="text-left px-6 text-xl"
|
||||
subTitle="After adding the details below, 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">
|
||||
<Image
|
||||
src="/images/integrations/Amazon Web Services.png"
|
||||
height={35}
|
||||
width={35}
|
||||
alt="AWS logo"
|
||||
/>
|
||||
</div>
|
||||
<span className="ml-1.5">AWS Parameter Store Integration </span>
|
||||
<Link href="https://infisical.com/docs/integrations/cloud/aws-parameter-store" 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="Access Key ID"
|
||||
errorText={accessKeyErrorText}
|
||||
isError={accessKeyErrorText !== "" ?? false}
|
||||
className="px-6"
|
||||
>
|
||||
<Input
|
||||
placeholder=""
|
||||
@@ -73,6 +107,7 @@ export default function AWSParameterStoreAuthorizeIntegrationPage() {
|
||||
label="Secret Access Key"
|
||||
errorText={accessSecretKeyErrorText}
|
||||
isError={accessSecretKeyErrorText !== "" ?? false}
|
||||
className="px-6"
|
||||
>
|
||||
<Input
|
||||
placeholder=""
|
||||
@@ -82,8 +117,9 @@ export default function AWSParameterStoreAuthorizeIntegrationPage() {
|
||||
</FormControl>
|
||||
<Button
|
||||
onClick={handleButtonClick}
|
||||
color="mineshaft"
|
||||
className="mt-4"
|
||||
colorSchema="primary"
|
||||
variant="outline_bg"
|
||||
className="mb-6 mt-2 ml-auto mr-6 w-min"
|
||||
isLoading={isLoading}
|
||||
>
|
||||
Connect to AWS Parameter Store
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
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, faCircleInfo } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import queryString from "query-string";
|
||||
|
||||
import {
|
||||
@@ -57,7 +62,7 @@ export default function AWSParameterStoreCreateIntegrationPage() {
|
||||
const { integrationAuthId } = queryString.parse(router.asPath.split("?")[1]);
|
||||
|
||||
const { data: workspace } = useGetWorkspaceById(localStorage.getItem("projectData.id") ?? "");
|
||||
const { data: integrationAuth } = useGetIntegrationAuthById((integrationAuthId as string) ?? "");
|
||||
const { data: integrationAuth, isLoading: isintegrationAuthLoading } = useGetIntegrationAuthById((integrationAuthId as string) ?? "");
|
||||
|
||||
const [selectedSourceEnvironment, setSelectedSourceEnvironment] = useState("");
|
||||
const [secretPath, setSecretPath] = useState("/");
|
||||
@@ -113,10 +118,38 @@ export default function AWSParameterStoreCreateIntegrationPage() {
|
||||
};
|
||||
|
||||
return integrationAuth && workspace && selectedSourceEnvironment ? (
|
||||
<div className="flex h-full w-full items-center justify-center">
|
||||
<Card className="max-w-md rounded-md p-8">
|
||||
<CardTitle className="text-center">AWS Parameter Store Integration</CardTitle>
|
||||
<FormControl label="Project Environment" className="mt-4">
|
||||
<div className="flex flex-col h-full w-full items-center justify-center">
|
||||
<Head>
|
||||
<title>Set Up AWS Parameter Integration</title>
|
||||
<link rel='icon' href='/infisical.ico' />
|
||||
</Head>
|
||||
<Card className="max-w-lg rounded-md border border-mineshaft-600">
|
||||
<CardTitle
|
||||
className="text-left px-6 text-xl"
|
||||
subTitle="Choose which environment in Infisical you want to sync to secerts in AWS Parameter Store."
|
||||
>
|
||||
<div className="flex flex-row items-center">
|
||||
<div className="inline flex items-center">
|
||||
<Image
|
||||
src="/images/integrations/Amazon Web Services.png"
|
||||
height={35}
|
||||
width={35}
|
||||
alt="AWS logo"
|
||||
/>
|
||||
</div>
|
||||
<span className="ml-1.5">AWS Parameter Store Integration </span>
|
||||
<Link href="https://infisical.com/docs/integrations/cloud/aws-parameter-store" 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="Project Environment" className="px-6">
|
||||
<Select
|
||||
value={selectedSourceEnvironment}
|
||||
onValueChange={(val) => setSelectedSourceEnvironment(val)}
|
||||
@@ -132,14 +165,14 @@ export default function AWSParameterStoreCreateIntegrationPage() {
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
<FormControl label="Secrets Path">
|
||||
<FormControl label="Secrets Path" className="px-6">
|
||||
<Input
|
||||
value={secretPath}
|
||||
onChange={(evt) => setSecretPath(evt.target.value)}
|
||||
placeholder="Provide a path, default is /"
|
||||
/>
|
||||
</FormControl>
|
||||
<FormControl label="AWS Region">
|
||||
<FormControl label="AWS Region" className="px-6">
|
||||
<Select
|
||||
value={selectedAWSRegion}
|
||||
onValueChange={(val) => setSelectedAWSRegion(val)}
|
||||
@@ -152,7 +185,7 @@ export default function AWSParameterStoreCreateIntegrationPage() {
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
<FormControl label="Path" errorText={pathErrorText} isError={pathErrorText !== "" ?? false}>
|
||||
<FormControl label="Path" errorText={pathErrorText} isError={pathErrorText !== "" ?? false} className="px-6">
|
||||
<Input
|
||||
placeholder={`/${workspace.name
|
||||
.toLowerCase()
|
||||
@@ -164,15 +197,39 @@ export default function AWSParameterStoreCreateIntegrationPage() {
|
||||
<Button
|
||||
onClick={handleButtonClick}
|
||||
color="mineshaft"
|
||||
className="mt-4"
|
||||
variant="outline_bg"
|
||||
className="mb-6 mt-2 ml-auto mr-6"
|
||||
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 AWS Parameter Store with secrets from Infisical.</span>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div />
|
||||
<div className="flex justify-center items-center w-full h-full">
|
||||
<Head>
|
||||
<title>Set Up AWS Parameter Store Integration</title>
|
||||
<link rel='icon' href='/infisical.ico' />
|
||||
</Head>
|
||||
{isintegrationAuthLoading ? <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:[email protected]"
|
||||
>
|
||||
support@infisical.com
|
||||
</a> if the issue persists.
|
||||
</p>
|
||||
</div>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
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";
|
||||
|
||||
@@ -54,12 +59,41 @@ export default function AWSSecretManagerCreateIntegrationPage() {
|
||||
|
||||
return (
|
||||
<div className="flex h-full w-full items-center justify-center">
|
||||
<Card className="max-w-md rounded-md p-8">
|
||||
<CardTitle className="mb-4 text-center">AWS Secret Manager Integration</CardTitle>
|
||||
<Head>
|
||||
<title>Authorize AWS Secrets Manager Integration</title>
|
||||
<link rel='icon' href='/infisical.ico' />
|
||||
</Head>
|
||||
<Card className="max-w-lg rounded-md border border-mineshaft-600">
|
||||
<CardTitle
|
||||
className="text-left px-6 text-xl"
|
||||
subTitle="After adding the details below, 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">
|
||||
<Image
|
||||
src="/images/integrations/Amazon Web Services.png"
|
||||
height={35}
|
||||
width={35}
|
||||
alt="AWS logo"
|
||||
/>
|
||||
</div>
|
||||
<span className="ml-1.5">AWS Secrets Manager Integration </span>
|
||||
<Link href="https://infisical.com/docs/integrations/cloud/aws-secret-manager" 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="Access Key ID"
|
||||
errorText={accessKeyErrorText}
|
||||
isError={accessKeyErrorText !== "" ?? false}
|
||||
className="px-6"
|
||||
>
|
||||
<Input placeholder="" value={accessKey} onChange={(e) => setAccessKey(e.target.value)} />
|
||||
</FormControl>
|
||||
@@ -67,6 +101,7 @@ export default function AWSSecretManagerCreateIntegrationPage() {
|
||||
label="Secret Access Key"
|
||||
errorText={accessSecretKeyErrorText}
|
||||
isError={accessSecretKeyErrorText !== "" ?? false}
|
||||
className="px-6"
|
||||
>
|
||||
<Input
|
||||
placeholder=""
|
||||
@@ -76,11 +111,12 @@ export default function AWSSecretManagerCreateIntegrationPage() {
|
||||
</FormControl>
|
||||
<Button
|
||||
onClick={handleButtonClick}
|
||||
color="mineshaft"
|
||||
className="mt-4"
|
||||
colorSchema="primary"
|
||||
variant="outline_bg"
|
||||
className="mb-6 mt-2 ml-auto mr-6 w-min"
|
||||
isLoading={isLoading}
|
||||
>
|
||||
Connect to AWS Secret Manager
|
||||
Connect to AWS Secrets Manager
|
||||
</Button>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
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, faCircleInfo } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import queryString from "query-string";
|
||||
|
||||
import {
|
||||
@@ -57,7 +62,7 @@ export default function AWSSecretManagerCreateIntegrationPage() {
|
||||
const { integrationAuthId } = queryString.parse(router.asPath.split("?")[1]);
|
||||
|
||||
const { data: workspace } = useGetWorkspaceById(localStorage.getItem("projectData.id") ?? "");
|
||||
const { data: integrationAuth } = useGetIntegrationAuthById((integrationAuthId as string) ?? "");
|
||||
const { data: integrationAuth, isLoading: isintegrationAuthLoading } = useGetIntegrationAuthById((integrationAuthId as string) ?? "");
|
||||
|
||||
const [selectedSourceEnvironment, setSelectedSourceEnvironment] = useState("");
|
||||
const [secretPath, setSecretPath] = useState("/");
|
||||
@@ -112,10 +117,38 @@ export default function AWSSecretManagerCreateIntegrationPage() {
|
||||
};
|
||||
|
||||
return integrationAuth && workspace && selectedSourceEnvironment ? (
|
||||
<div className="flex h-full w-full items-center justify-center">
|
||||
<Card className="max-w-md rounded-md p-8">
|
||||
<CardTitle className="text-center">AWS Secret Manager Integration</CardTitle>
|
||||
<FormControl label="Project Environment" className="mt-4">
|
||||
<div className="flex flex-col h-full w-full items-center justify-center">
|
||||
<Head>
|
||||
<title>Set Up AWS Secrets Manager Integration</title>
|
||||
<link rel='icon' href='/infisical.ico' />
|
||||
</Head>
|
||||
<Card className="max-w-lg rounded-md border border-mineshaft-600">
|
||||
<CardTitle
|
||||
className="text-left px-6 text-xl"
|
||||
subTitle="Choose which environment in Infisical you want to sync to secerts in AWS Secrets Manager."
|
||||
>
|
||||
<div className="flex flex-row items-center">
|
||||
<div className="inline flex items-center">
|
||||
<Image
|
||||
src="/images/integrations/Amazon Web Services.png"
|
||||
height={35}
|
||||
width={35}
|
||||
alt="AWS logo"
|
||||
/>
|
||||
</div>
|
||||
<span className="ml-1.5">AWS Secrets Manager Integration </span>
|
||||
<Link href="https://infisical.com/docs/integrations/cloud/aws-secret-manager" 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="Project Environment" className="px-6">
|
||||
<Select
|
||||
value={selectedSourceEnvironment}
|
||||
onValueChange={(val) => setSelectedSourceEnvironment(val)}
|
||||
@@ -131,14 +164,14 @@ export default function AWSSecretManagerCreateIntegrationPage() {
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
<FormControl label="Secrets Path">
|
||||
<FormControl label="Secrets Path" className="px-6">
|
||||
<Input
|
||||
value={secretPath}
|
||||
onChange={(evt) => setSecretPath(evt.target.value)}
|
||||
placeholder="Provide a path, default is /"
|
||||
/>
|
||||
</FormControl>
|
||||
<FormControl label="AWS Region">
|
||||
<FormControl label="AWS Region" className="px-6">
|
||||
<Select
|
||||
value={selectedAWSRegion}
|
||||
onValueChange={(val) => setSelectedAWSRegion(val)}
|
||||
@@ -155,6 +188,7 @@ export default function AWSSecretManagerCreateIntegrationPage() {
|
||||
label="AWS SM Secret Name"
|
||||
errorText={targetSecretNameErrorText}
|
||||
isError={targetSecretNameErrorText !== "" ?? false}
|
||||
className="px-6"
|
||||
>
|
||||
<Input
|
||||
placeholder={`${workspace.name
|
||||
@@ -167,15 +201,39 @@ export default function AWSSecretManagerCreateIntegrationPage() {
|
||||
<Button
|
||||
onClick={handleButtonClick}
|
||||
color="mineshaft"
|
||||
className="mt-4"
|
||||
variant="outline_bg"
|
||||
className="mb-6 mt-2 ml-auto mr-6"
|
||||
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 AWS Secrets Manager with secrets from Infisical.</span>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div />
|
||||
<div className="flex justify-center items-center w-full h-full">
|
||||
<Head>
|
||||
<title>Set Up AWS Secrets Manager Integration</title>
|
||||
<link rel='icon' href='/infisical.ico' />
|
||||
</Head>
|
||||
{isintegrationAuthLoading ? <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:[email protected]"
|
||||
>
|
||||
support@infisical.com
|
||||
</a> if the issue persists.
|
||||
</p>
|
||||
</div>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
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
|
||||
@@ -41,8 +46,36 @@ export default function ChecklyCreateIntegrationPage() {
|
||||
|
||||
return (
|
||||
<div className="flex h-full w-full items-center justify-center">
|
||||
<Head>
|
||||
<title>Authorize Checkly 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" subTitle="After adding your API-key, you will be prompted to set up an integration for a particular Infisical project and environment.">Checkly Integration</CardTitle>
|
||||
<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/Checkly.png"
|
||||
height={30}
|
||||
width={30}
|
||||
alt="Checkly logo"
|
||||
/>
|
||||
</div>
|
||||
<span className="ml-2.5">Checkly Integration </span>
|
||||
<Link href="https://infisical.com/docs/integrations/cloud/checkly" 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="Checkly API key"
|
||||
errorText={accessTokenErrorText}
|
||||
@@ -57,7 +90,7 @@ export default function ChecklyCreateIntegrationPage() {
|
||||
</FormControl>
|
||||
<Button
|
||||
onClick={handleButtonClick}
|
||||
color="mineshaft"
|
||||
colorSchema="primary"
|
||||
variant="outline_bg"
|
||||
className="mb-6 mt-2 ml-auto mr-6 w-min"
|
||||
isFullWidth={false}
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
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, faCircleInfo } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { motion } from "framer-motion";
|
||||
import queryString from "query-string";
|
||||
|
||||
import {
|
||||
@@ -9,7 +15,11 @@ import {
|
||||
FormControl,
|
||||
Input,
|
||||
Select,
|
||||
SelectItem
|
||||
SelectItem,
|
||||
Tab,
|
||||
TabList,
|
||||
TabPanel,
|
||||
Tabs
|
||||
} from "@app/components/v2";
|
||||
import {
|
||||
useCreateIntegration
|
||||
@@ -21,6 +31,11 @@ import {
|
||||
} from "../../../hooks/api/integrationAuth";
|
||||
import { useGetWorkspaceById } from "../../../hooks/api/workspace";
|
||||
|
||||
enum TabSections {
|
||||
Connection = "connection",
|
||||
Options = "options"
|
||||
}
|
||||
|
||||
export default function ChecklyCreateIntegrationPage() {
|
||||
const router = useRouter();
|
||||
const { mutateAsync } = useCreateIntegration();
|
||||
@@ -29,7 +44,7 @@ export default function ChecklyCreateIntegrationPage() {
|
||||
|
||||
const { data: workspace } = useGetWorkspaceById(localStorage.getItem("projectData.id") ?? "");
|
||||
const { data: integrationAuth } = useGetIntegrationAuthById((integrationAuthId as string) ?? "");
|
||||
const { data: integrationAuthApps } = useGetIntegrationAuthApps({
|
||||
const { data: integrationAuthApps, isLoading: isIntegrationAuthAppsLoading } = useGetIntegrationAuthApps({
|
||||
integrationAuthId: (integrationAuthId as string) ?? ""
|
||||
});
|
||||
|
||||
@@ -91,81 +106,156 @@ export default function ChecklyCreateIntegrationPage() {
|
||||
selectedSourceEnvironment &&
|
||||
integrationAuthApps &&
|
||||
targetApp ? (
|
||||
<div className="flex h-full w-full items-center justify-center bg-gradient-to-tr from-mineshaft-900 to-bunker-900">
|
||||
<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 Checkly Integration</title>
|
||||
<link rel='icon' href='/infisical.ico' />
|
||||
</Head>
|
||||
<Card className="max-w-lg rounded-md border border-mineshaft-600 p-0">
|
||||
<CardTitle
|
||||
className="px-6 text-left"
|
||||
subTitle="Choose which environment in Infisical you want to sync with your Checkly account."
|
||||
className="text-left px-6 text-xl"
|
||||
subTitle="Choose which environment in Infisical you want to sync to Checkly environment variables."
|
||||
>
|
||||
Checkly Integration
|
||||
<div className="flex flex-row items-center">
|
||||
<div className="inline flex items-center pb-0.5">
|
||||
<Image
|
||||
src="/images/integrations/Checkly.png"
|
||||
height={30}
|
||||
width={30}
|
||||
alt="Checkly logo"
|
||||
/>
|
||||
</div>
|
||||
<span className="ml-2.5">Checkly Integration </span>
|
||||
<Link href="https://infisical.com/docs/integrations/cloud/checkly" 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="Infisical Project Environment" className="mt-4 px-6">
|
||||
<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="mt-4 px-6">
|
||||
<Input
|
||||
value={secretPath}
|
||||
onChange={(evt) => setSecretPath(evt.target.value)}
|
||||
placeholder="Provide a path, default is /"
|
||||
/>
|
||||
</FormControl>
|
||||
<FormControl label="Checkly Account" className="mt-4 px-6">
|
||||
<Select
|
||||
value={targetApp}
|
||||
onValueChange={(val) => setTargetApp(val)}
|
||||
className="w-full border border-mineshaft-500"
|
||||
isDisabled={integrationAuthApps.length === 0}
|
||||
>
|
||||
{integrationAuthApps.length > 0 ? (
|
||||
integrationAuthApps.map((integrationAuthApp) => (
|
||||
<SelectItem
|
||||
value={integrationAuthApp.name}
|
||||
key={`target-app-${integrationAuthApp.name}`}
|
||||
<Tabs defaultValue={TabSections.Connection} className="px-6">
|
||||
<TabList>
|
||||
<div className="flex flex-row border-b border-mineshaft-600 w-full">
|
||||
<Tab value={TabSections.Connection}>Connection</Tab>
|
||||
<Tab value={TabSections.Options}>Options</Tab>
|
||||
</div>
|
||||
</TabList>
|
||||
<TabPanel value={TabSections.Connection}>
|
||||
<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"
|
||||
>
|
||||
{integrationAuthApp.name}
|
||||
</SelectItem>
|
||||
))
|
||||
) : (
|
||||
<SelectItem value="none" key="target-app-none">
|
||||
No apps found
|
||||
</SelectItem>
|
||||
)}
|
||||
</Select>
|
||||
</FormControl>
|
||||
<FormControl label="Append the Secret Name with..." className="mt-4 px-6">
|
||||
<Input
|
||||
value={secretSuffix}
|
||||
onChange={(evt) => setSecretSuffix(evt.target.value)}
|
||||
placeholder="Provide a suffix for secret names, default is no suffix"
|
||||
/>
|
||||
</FormControl>
|
||||
{workspace?.environments.map((sourceEnvironment) => (
|
||||
<SelectItem
|
||||
value={sourceEnvironment.slug}
|
||||
key={`source-environment-${sourceEnvironment.slug}`}
|
||||
>
|
||||
{sourceEnvironment.name}
|
||||
</SelectItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
<FormControl label="Secrets Path">
|
||||
<Input
|
||||
value={secretPath}
|
||||
onChange={(evt) => setSecretPath(evt.target.value)}
|
||||
placeholder="Provide a path, default is /"
|
||||
/>
|
||||
</FormControl>
|
||||
<FormControl label="Checkly Account">
|
||||
<Select
|
||||
value={targetApp}
|
||||
onValueChange={(val) => setTargetApp(val)}
|
||||
className="w-full border border-mineshaft-500"
|
||||
isDisabled={integrationAuthApps.length === 0}
|
||||
>
|
||||
{integrationAuthApps.length > 0 ? (
|
||||
integrationAuthApps.map((integrationAuthApp) => (
|
||||
<SelectItem
|
||||
value={integrationAuthApp.name}
|
||||
key={`target-app-${integrationAuthApp.name}`}
|
||||
>
|
||||
{integrationAuthApp.name}
|
||||
</SelectItem>
|
||||
))
|
||||
) : (
|
||||
<SelectItem value="none" key="target-app-none">
|
||||
No apps found
|
||||
</SelectItem>
|
||||
)}
|
||||
</Select>
|
||||
</FormControl>
|
||||
</motion.div>
|
||||
</TabPanel>
|
||||
<TabPanel value={TabSections.Options}>
|
||||
<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="Append Secret Names with..." className="pb-[9.75rem]">
|
||||
<Input
|
||||
value={secretSuffix}
|
||||
onChange={(evt) => setSecretSuffix(evt.target.value)}
|
||||
placeholder="Provide a suffix for secret names, default is no suffix"
|
||||
/>
|
||||
</FormControl>
|
||||
</motion.div>
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
<Button
|
||||
onClick={handleButtonClick}
|
||||
color="mineshaft"
|
||||
variant="outline_bg"
|
||||
className="mt-2 mb-6 ml-auto mr-6"
|
||||
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 Checkly with secrets from Infisical.</span>
|
||||
<span className="text-mineshaft-300 text-sm mt-4">If you have multiple Checkly integrations and are using suffixes for at least one of them, you will have to add suffixes for all the active Checkly integrations – otherwise you might run into rare unexpected behavior.</span>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div />
|
||||
<div className="flex justify-center items-center w-full h-full">
|
||||
<Head>
|
||||
<title>Set Up Checkly 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:[email protected]"
|
||||
>
|
||||
support@infisical.com
|
||||
</a> if the issue persists.
|
||||
</p>
|
||||
</div>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
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
|
||||
@@ -41,19 +46,49 @@ export default function CircleCICreateIntegrationPage() {
|
||||
|
||||
return (
|
||||
<div className="flex h-full w-full items-center justify-center">
|
||||
<Card className="max-w-md rounded-md p-8">
|
||||
<CardTitle className="text-center">CircleCI Integration</CardTitle>
|
||||
<Head>
|
||||
<title>Authorize CircleCI 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 Token, 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/Circle CI.png"
|
||||
height={30}
|
||||
width={30}
|
||||
alt="CircleCI logo"
|
||||
/>
|
||||
</div>
|
||||
<span className="ml-1.5">CircleCI Integration </span>
|
||||
<Link href="https://infisical.com/docs/integrations/cicd/circleci" 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="CircleCI API Token"
|
||||
errorText={apiKeyErrorText}
|
||||
isError={apiKeyErrorText !== "" ?? false}
|
||||
className="px-6"
|
||||
>
|
||||
<Input placeholder="" value={apiKey} onChange={(e) => setApiKey(e.target.value)} />
|
||||
</FormControl>
|
||||
<Button
|
||||
onClick={handleButtonClick}
|
||||
color="mineshaft"
|
||||
className="mt-4"
|
||||
colorSchema="primary"
|
||||
variant="outline_bg"
|
||||
className="mb-6 mt-2 ml-auto mr-6 w-min"
|
||||
isLoading={isLoading}
|
||||
>
|
||||
Connect to CircleCI
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
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, faCircleInfo } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import queryString from "query-string";
|
||||
|
||||
import {
|
||||
@@ -28,8 +33,8 @@ export default function CircleCICreateIntegrationPage() {
|
||||
const { integrationAuthId } = queryString.parse(router.asPath.split("?")[1]);
|
||||
|
||||
const { data: workspace } = useGetWorkspaceById(localStorage.getItem("projectData.id") ?? "");
|
||||
const { data: integrationAuth } = useGetIntegrationAuthById((integrationAuthId as string) ?? "");
|
||||
const { data: integrationAuthApps } = useGetIntegrationAuthApps({
|
||||
const { data: integrationAuth, isLoading: isintegrationAuthLoading } = useGetIntegrationAuthById((integrationAuthId as string) ?? "");
|
||||
const { data: integrationAuthApps, isLoading: isIntegrationAuthAppsLoading } = useGetIntegrationAuthApps({
|
||||
integrationAuthId: (integrationAuthId as string) ?? ""
|
||||
});
|
||||
|
||||
@@ -84,10 +89,38 @@ export default function CircleCICreateIntegrationPage() {
|
||||
selectedSourceEnvironment &&
|
||||
integrationAuthApps &&
|
||||
targetApp ? (
|
||||
<div className="flex h-full w-full items-center justify-center">
|
||||
<Card className="max-w-md rounded-md p-8">
|
||||
<CardTitle className="text-center">CircleCI Integration</CardTitle>
|
||||
<FormControl label="Project Environment" className="mt-4">
|
||||
<div className="flex flex-col h-full w-full items-center justify-center">
|
||||
<Head>
|
||||
<title>Set Up CircleCI Integration</title>
|
||||
<link rel='icon' href='/infisical.ico' />
|
||||
</Head>
|
||||
<Card className="max-w-lg rounded-md border border-mineshaft-600">
|
||||
<CardTitle
|
||||
className="text-left px-6 text-xl"
|
||||
subTitle="Choose which environment or folder in Infisical you want to sync to CircleCI environment variables."
|
||||
>
|
||||
<div className="flex flex-row items-center">
|
||||
<div className="inline flex items-center pb-0.5">
|
||||
<Image
|
||||
src="/images/integrations/Circle CI.png"
|
||||
height={30}
|
||||
width={30}
|
||||
alt="CircleCI logo"
|
||||
/>
|
||||
</div>
|
||||
<span className="ml-1.5">CircleCI Integration </span>
|
||||
<Link href="https://infisical.com/docs/integrations/cicd/circleci" 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="Project Environment" className="px-6">
|
||||
<Select
|
||||
value={selectedSourceEnvironment}
|
||||
onValueChange={(val) => setSelectedSourceEnvironment(val)}
|
||||
@@ -103,14 +136,14 @@ export default function CircleCICreateIntegrationPage() {
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
<FormControl label="Secrets Path">
|
||||
<FormControl label="Secrets Path" className="px-6">
|
||||
<Input
|
||||
value={secretPath}
|
||||
onChange={(evt) => setSecretPath(evt.target.value)}
|
||||
placeholder="Provide a path, default is /"
|
||||
/>
|
||||
</FormControl>
|
||||
<FormControl label="CircleCI Project" className="mt-4">
|
||||
<FormControl label="CircleCI Project" className="px-6">
|
||||
<Select
|
||||
value={targetApp}
|
||||
onValueChange={(val) => setTargetApp(val)}
|
||||
@@ -135,17 +168,41 @@ export default function CircleCICreateIntegrationPage() {
|
||||
</FormControl>
|
||||
<Button
|
||||
onClick={handleButtonClick}
|
||||
color="mineshaft"
|
||||
className="mt-4"
|
||||
colorSchema="primary"
|
||||
variant="outline_bg"
|
||||
className="mb-6 mt-2 ml-auto mr-6 w-min"
|
||||
isLoading={isLoading}
|
||||
isDisabled={integrationAuthApps.length === 0}
|
||||
>
|
||||
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 CircleCI with secrets from Infisical.</span>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div />
|
||||
<div className="flex justify-center items-center w-full h-full">
|
||||
<Head>
|
||||
<title>Set Up CircleCI Integration</title>
|
||||
<link rel='icon' href='/infisical.ico' />
|
||||
</Head>
|
||||
{isIntegrationAuthAppsLoading || isintegrationAuthLoading ? <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:[email protected]"
|
||||
>
|
||||
support@infisical.com
|
||||
</a> if the issue persists.
|
||||
</p>
|
||||
</div>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
import { useState } from "react";
|
||||
import { Controller, useForm } from "react-hook-form";
|
||||
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 { yupResolver } from "@hookform/resolvers/yup";
|
||||
import * as yup from "yup";
|
||||
|
||||
import {
|
||||
useSaveIntegrationAccessToken
|
||||
@@ -7,22 +15,34 @@ import {
|
||||
|
||||
import { Button, Card, CardTitle, FormControl, Input } from "../../../components/v2";
|
||||
|
||||
export default function FlyioCreateIntegrationPage() {
|
||||
const schema = yup.object({
|
||||
accessToken: yup.string().trim().required("Fly.io Access Token is required")
|
||||
});
|
||||
|
||||
type FormData = yup.InferType<typeof schema>;
|
||||
|
||||
export default function FlyioAuthorizeIntegrationPage() {
|
||||
const router = useRouter();
|
||||
|
||||
const {
|
||||
control,
|
||||
handleSubmit
|
||||
} = useForm<FormData>({
|
||||
resolver: yupResolver(schema),
|
||||
defaultValues: {
|
||||
accessToken: ""
|
||||
}
|
||||
});
|
||||
|
||||
const { mutateAsync } = useSaveIntegrationAccessToken();
|
||||
|
||||
const [accessToken, setAccessToken] = useState("");
|
||||
const [accessTokenErrorText, setAccessTokenErrorText] = useState("");
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
const handleButtonClick = async () => {
|
||||
const onFormSubmit = async ({
|
||||
accessToken
|
||||
}: FormData) => {
|
||||
console.log("onFormSubmit accessToken: ", accessToken);
|
||||
try {
|
||||
setAccessTokenErrorText("");
|
||||
if (accessToken.length === 0) {
|
||||
setAccessTokenErrorText("Access token cannot be blank");
|
||||
return;
|
||||
}
|
||||
|
||||
setIsLoading(true);
|
||||
|
||||
const integrationAuth = await mutateAsync({
|
||||
@@ -32,39 +52,79 @@ export default function FlyioCreateIntegrationPage() {
|
||||
});
|
||||
|
||||
setIsLoading(false);
|
||||
|
||||
router.push(`/integrations/flyio/create?integrationAuthId=${integrationAuth._id}`);
|
||||
} catch (err) {
|
||||
setIsLoading(false);
|
||||
console.error(err);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex h-full w-full items-center justify-center">
|
||||
<Card className="max-w-md rounded-md p-8">
|
||||
<CardTitle className="text-center">Fly.io Integration</CardTitle>
|
||||
<FormControl
|
||||
label="Fly.io Access Token"
|
||||
errorText={accessTokenErrorText}
|
||||
isError={accessTokenErrorText !== "" ?? false}
|
||||
<Head>
|
||||
<title>Authorize Fly.io 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 access token, you will be prompted to set up an integration for a particular Infisical project and environment."
|
||||
>
|
||||
<Input
|
||||
placeholder=""
|
||||
value={accessToken}
|
||||
onChange={(e) => setAccessToken(e.target.value)}
|
||||
<div className="flex flex-row items-center">
|
||||
<div className="inline flex items-center pb-0.5">
|
||||
<Image
|
||||
src="/images/integrations/Flyio.svg"
|
||||
height={30}
|
||||
width={30}
|
||||
alt="Fly.io logo"
|
||||
/>
|
||||
</div>
|
||||
<span className="ml-2.5">Fly.io Integration </span>
|
||||
<Link href="https://infisical.com/docs/integrations/cloud/flyio" 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>
|
||||
<form
|
||||
onSubmit={handleSubmit(onFormSubmit)}
|
||||
className="px-6 text-right pb-8"
|
||||
>
|
||||
<Controller
|
||||
control={control}
|
||||
name="accessToken"
|
||||
render={({ field, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
label="Fly.io Access Token"
|
||||
errorText={error?.message}
|
||||
isError={Boolean(error)}
|
||||
>
|
||||
<Input
|
||||
{...field}
|
||||
placeholder=""
|
||||
/>
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
</FormControl>
|
||||
<Button
|
||||
onClick={handleButtonClick}
|
||||
color="mineshaft"
|
||||
className="mt-4"
|
||||
isLoading={isLoading}
|
||||
>
|
||||
Connect to Fly.io
|
||||
</Button>
|
||||
<Button
|
||||
colorSchema="primary"
|
||||
variant="outline_bg"
|
||||
className="mt-2 w-min"
|
||||
size="sm"
|
||||
type="submit"
|
||||
isLoading={isLoading}
|
||||
>
|
||||
Connect to Fly.io
|
||||
</Button>
|
||||
</form>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
FlyioCreateIntegrationPage.requireAuth = true;
|
||||
FlyioAuthorizeIntegrationPage.requireAuth = true;
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
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, faCircleInfo } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import queryString from "query-string";
|
||||
|
||||
import {
|
||||
@@ -23,13 +28,14 @@ import { useGetWorkspaceById } from "../../../hooks/api/workspace";
|
||||
|
||||
export default function FlyioCreateIntegrationPage() {
|
||||
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 { data: integrationAuthApps } = useGetIntegrationAuthApps({
|
||||
const { data: integrationAuth, isLoading: isIntegrationAuthLoading } = useGetIntegrationAuthById((integrationAuthId as string) ?? "");
|
||||
const { data: integrationAuthApps, isLoading: isIntegrationAuthAppsLoading } = useGetIntegrationAuthApps({
|
||||
integrationAuthId: (integrationAuthId as string) ?? ""
|
||||
});
|
||||
|
||||
@@ -84,10 +90,38 @@ export default function FlyioCreateIntegrationPage() {
|
||||
selectedSourceEnvironment &&
|
||||
integrationAuthApps &&
|
||||
targetApp ? (
|
||||
<div className="flex h-full w-full items-center justify-center">
|
||||
<Card className="max-w-md rounded-md p-8">
|
||||
<CardTitle className="text-center">Fly.io Integration</CardTitle>
|
||||
<FormControl label="Project Environment" className="mt-4">
|
||||
<div className="flex flex-col h-full w-full items-center justify-center">
|
||||
<Head>
|
||||
<title>Set Up Fly.io Integration</title>
|
||||
<link rel='icon' href='/infisical.ico' />
|
||||
</Head>
|
||||
<Card className="max-w-lg rounded-md border border-mineshaft-600">
|
||||
<CardTitle
|
||||
className="text-left px-6 text-xl"
|
||||
subTitle="Choose which environment or folder in Infisical you want to sync to Fly.io environment variables."
|
||||
>
|
||||
<div className="flex flex-row items-center">
|
||||
<div className="inline flex items-center pb-0.5">
|
||||
<Image
|
||||
src="/images/integrations/Flyio.svg"
|
||||
height={30}
|
||||
width={30}
|
||||
alt="Fly.io logo"
|
||||
/>
|
||||
</div>
|
||||
<span className="ml-2.5">Fly.io Integration </span>
|
||||
<Link href="https://infisical.com/docs/integrations/cloud/flyio" 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="Project Environment" className="px-6">
|
||||
<Select
|
||||
value={selectedSourceEnvironment}
|
||||
onValueChange={(val) => setSelectedSourceEnvironment(val)}
|
||||
@@ -103,14 +137,14 @@ export default function FlyioCreateIntegrationPage() {
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
<FormControl label="Secrets Path">
|
||||
<FormControl label="Secrets Path" className="px-6">
|
||||
<Input
|
||||
value={secretPath}
|
||||
onChange={(evt) => setSecretPath(evt.target.value)}
|
||||
placeholder="Provide a path, default is /"
|
||||
/>
|
||||
</FormControl>
|
||||
<FormControl label="Fly.io App" className="mt-4">
|
||||
<FormControl label="Fly.io App" className="px-6">
|
||||
<Select
|
||||
value={targetApp}
|
||||
onValueChange={(val) => setTargetApp(val)}
|
||||
@@ -136,16 +170,40 @@ export default function FlyioCreateIntegrationPage() {
|
||||
<Button
|
||||
onClick={handleButtonClick}
|
||||
color="mineshaft"
|
||||
className="mt-4"
|
||||
variant="outline_bg"
|
||||
className="mb-6 mt-2 ml-auto mr-6"
|
||||
isLoading={isLoading}
|
||||
isDisabled={integrationAuthApps.length === 0}
|
||||
>
|
||||
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 Fly.io with secrets from Infisical.</span>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div />
|
||||
<div className="flex justify-center items-center w-full h-full">
|
||||
<Head>
|
||||
<title>Set Up Fly.io Integration</title>
|
||||
<link rel='icon' href='/infisical.ico' />
|
||||
</Head>
|
||||
{isIntegrationAuthLoading || 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:[email protected]"
|
||||
>
|
||||
support@infisical.com
|
||||
</a> if the issue persists.
|
||||
</p>
|
||||
</div>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,16 @@
|
||||
import crypto from "crypto";
|
||||
|
||||
import { useState } from "react";
|
||||
import { Controller, useForm } from "react-hook-form";
|
||||
import Head from "next/head";
|
||||
import Image from "next/image";
|
||||
import Link from "next/link";
|
||||
import { useRouter } from "next/router";
|
||||
import { faGoogle } from "@fortawesome/free-brands-svg-icons";
|
||||
import { faArrowUpRightFromSquare, faBookOpen } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { yupResolver } from "@hookform/resolvers/yup";
|
||||
import * as yup from "yup";
|
||||
|
||||
import {
|
||||
useGetCloudIntegrations,
|
||||
@@ -11,40 +18,28 @@ import {
|
||||
|
||||
import { Button, Card, CardTitle, FormControl, TextArea } from "../../../components/v2";
|
||||
|
||||
const schema = yup.object({
|
||||
accessToken: yup.string().required("Service Account JSON cannot be empty")
|
||||
});
|
||||
|
||||
type FormData = yup.InferType<typeof schema>;
|
||||
|
||||
export default function GCPSecretManagerAuthorizeIntegrationPage() {
|
||||
const router = useRouter();
|
||||
|
||||
const {
|
||||
control,
|
||||
handleSubmit
|
||||
} = useForm<FormData>({
|
||||
resolver: yupResolver(schema)
|
||||
});
|
||||
|
||||
const { data: cloudIntegrations } = useGetCloudIntegrations();
|
||||
|
||||
const { mutateAsync } = useSaveIntegrationAccessToken();
|
||||
|
||||
const [accessToken, setAccessToken] = useState("");
|
||||
const [accessTokenErrorText, setAccessTokenErrorText] = useState("");
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
const handleIntegrateWithPAT = async () => {
|
||||
try {
|
||||
setAccessTokenErrorText("");
|
||||
if (accessToken.length === 0) {
|
||||
setAccessTokenErrorText("Service account JSON cannot be blank");
|
||||
return;
|
||||
}
|
||||
|
||||
setIsLoading(true);
|
||||
|
||||
const integrationAuth = await mutateAsync({
|
||||
workspaceId: localStorage.getItem("projectData.id"),
|
||||
integration: "gcp-secret-manager",
|
||||
refreshToken: accessToken
|
||||
});
|
||||
|
||||
setIsLoading(false);
|
||||
|
||||
router.push(`/integrations/gcp-secret-manager/pat/create?integrationAuthId=${integrationAuth._id}`);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
};
|
||||
|
||||
const handleIntegrateWithOAuth = () => {
|
||||
if (!cloudIntegrations) return;
|
||||
const integrationOption = cloudIntegrations.find((integration) => integration.slug === "gcp-secret-manager");
|
||||
@@ -58,45 +53,105 @@ export default function GCPSecretManagerAuthorizeIntegrationPage() {
|
||||
window.location.assign(link);
|
||||
}
|
||||
|
||||
const onFormSubmit = async ({
|
||||
accessToken
|
||||
}: FormData) => {
|
||||
try {
|
||||
setIsLoading(true);
|
||||
|
||||
const integrationAuth = await mutateAsync({
|
||||
workspaceId: localStorage.getItem("projectData.id"),
|
||||
integration: "gcp-secret-manager",
|
||||
refreshToken: accessToken
|
||||
});
|
||||
|
||||
setIsLoading(false);
|
||||
router.push(`/integrations/gcp-secret-manager/create?integrationAuthId=${integrationAuth._id}`);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
setIsLoading(false);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex h-full w-full items-center justify-center">
|
||||
<Card className="max-w-md rounded-md p-8">
|
||||
<CardTitle className="text-center">GCP Secret Manager Integration</CardTitle>
|
||||
<Button
|
||||
<Head>
|
||||
<title>Authorize GCP Secret Manager 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="Connect Infisical to GCP Secret Manager to sync secrets."
|
||||
>
|
||||
<div className="flex flex-row items-center">
|
||||
<div className="inline flex items-center pb-0.5">
|
||||
<Image
|
||||
src="/images/integrations/Google Cloud Platform.png"
|
||||
height={30}
|
||||
width={30}
|
||||
alt="GCP logo"
|
||||
/>
|
||||
</div>
|
||||
<span className="ml-1.5">GCP Secret Manager Integration </span>
|
||||
<Link href="https://infisical.com/docs/integrations/cloud/gcp-secret-manager" 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>
|
||||
<div className="px-6">
|
||||
<Button
|
||||
colorSchema="primary"
|
||||
variant="outline_bg"
|
||||
onClick={handleIntegrateWithOAuth}
|
||||
leftIcon={<FontAwesomeIcon icon={faGoogle} className="mr-2" />}
|
||||
className="h-11 w-full mx-0 mt-4"
|
||||
>
|
||||
Continue with OAuth
|
||||
</Button>
|
||||
<div className='w-full flex flex-row items-center my-4 py-2'>
|
||||
<div className='w-full border-t border-mineshaft-400/40' />
|
||||
<span className="mx-2 text-mineshaft-400 text-xs">or</span>
|
||||
<div className='w-full border-t border-mineshaft-400/40' />
|
||||
</div>
|
||||
</div>
|
||||
<form
|
||||
onSubmit={handleSubmit(onFormSubmit)}
|
||||
className="px-6 text-right pb-8"
|
||||
>
|
||||
<Controller
|
||||
control={control}
|
||||
name="accessToken"
|
||||
render={({ field, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
label="GCP Service Account JSON"
|
||||
errorText={error?.message}
|
||||
isError={Boolean(error)}
|
||||
>
|
||||
<TextArea
|
||||
{...field}
|
||||
className="h-48 border border-mineshaft-600 bg-bunker-900/80"
|
||||
/>
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
<Button
|
||||
colorSchema="primary"
|
||||
variant="outline_bg"
|
||||
onClick={handleIntegrateWithOAuth}
|
||||
leftIcon={<FontAwesomeIcon icon={faGoogle} className="mr-2" />}
|
||||
className="h-11 w-full mx-0 mt-4"
|
||||
>
|
||||
Continue with OAuth
|
||||
</Button>
|
||||
<div className='w-full flex flex-row items-center my-4 py-2'>
|
||||
<div className='w-full border-t border-mineshaft-400/60' />
|
||||
<span className="mx-2 text-mineshaft-200 text-xs">or</span>
|
||||
<div className='w-full border-t border-mineshaft-400/60' />
|
||||
</div>
|
||||
<FormControl
|
||||
label="GCP Service Account JSON"
|
||||
errorText={accessTokenErrorText}
|
||||
isError={accessTokenErrorText !== "" ?? false}
|
||||
>
|
||||
<TextArea
|
||||
value={accessToken}
|
||||
onChange={(e) => setAccessToken(e.target.value)}
|
||||
placeholder=""
|
||||
className="h-48"
|
||||
/>
|
||||
</FormControl>
|
||||
<Button
|
||||
onClick={handleIntegrateWithPAT}
|
||||
color="mineshaft"
|
||||
className="mt-4"
|
||||
isLoading={isLoading}
|
||||
>
|
||||
Connect to GCP Secret Manager
|
||||
</Button>
|
||||
|
||||
className="mt-2 w-min"
|
||||
size="sm"
|
||||
type="submit"
|
||||
isLoading={isLoading}
|
||||
>
|
||||
Connect to GCP Secret Manager
|
||||
</Button>
|
||||
</form>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,7 +1,17 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { Controller, useForm } from "react-hook-form";
|
||||
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 { yupResolver } from "@hookform/resolvers/yup";
|
||||
import { motion } from "framer-motion";
|
||||
import queryString from "query-string";
|
||||
import * as yup from "yup";
|
||||
|
||||
import { usePopUp } from "@app/hooks";
|
||||
import {
|
||||
useCreateIntegration
|
||||
} from "@app/hooks/api";
|
||||
@@ -12,50 +22,114 @@ import {
|
||||
CardTitle,
|
||||
FormControl,
|
||||
Input,
|
||||
Modal,
|
||||
ModalContent,
|
||||
Select,
|
||||
SelectItem
|
||||
} from "../../../components/v2";
|
||||
SelectItem,
|
||||
Switch,
|
||||
Tab,
|
||||
TabList,
|
||||
TabPanel,
|
||||
Tabs} from "../../../components/v2";
|
||||
import {
|
||||
useGetIntegrationAuthApps,
|
||||
useGetIntegrationAuthById
|
||||
} from "../../../hooks/api/integrationAuth";
|
||||
import { useGetWorkspaceById } from "../../../hooks/api/workspace";
|
||||
|
||||
enum TabSections {
|
||||
Connection = "connection",
|
||||
Options = "options"
|
||||
}
|
||||
|
||||
const schema = yup.object({
|
||||
selectedSourceEnvironment: yup.string().required("Source environment is required"),
|
||||
secretPath: yup.string().required("Secret path is required"),
|
||||
targetAppId: yup.string().required("GCP project is required"),
|
||||
secretPrefix: yup.string().trim(),
|
||||
secretSuffix: yup.string().trim(),
|
||||
shouldLabel: yup.boolean(),
|
||||
labelName: yup.string().trim(),
|
||||
labelValue: yup.string().trim()
|
||||
});
|
||||
|
||||
type FormData = yup.InferType<typeof schema>;
|
||||
|
||||
export default function GCPSecretManagerCreateIntegrationPage() {
|
||||
const router = useRouter();
|
||||
const { popUp, handlePopUpOpen, handlePopUpToggle, handlePopUpClose } = usePopUp([
|
||||
"confirmIntegration"
|
||||
] as const);
|
||||
|
||||
const {
|
||||
control,
|
||||
handleSubmit,
|
||||
setValue,
|
||||
watch
|
||||
} = useForm<FormData>({
|
||||
resolver: yupResolver(schema),
|
||||
defaultValues: {
|
||||
secretPath: "/",
|
||||
secretPrefix: "",
|
||||
secretSuffix: "",
|
||||
shouldLabel: false,
|
||||
labelName: "managed-by",
|
||||
labelValue: "infisical"
|
||||
}
|
||||
});
|
||||
|
||||
const shouldLabel = watch("shouldLabel");
|
||||
const selectedSourceEnvironment = watch("selectedSourceEnvironment");
|
||||
|
||||
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 { data: integrationAuthApps } = useGetIntegrationAuthApps({
|
||||
const { data: integrationAuthApps, isLoading: isIntegrationAuthAppsLoading } = useGetIntegrationAuthApps({
|
||||
integrationAuthId: (integrationAuthId as string) ?? ""
|
||||
});
|
||||
|
||||
const [selectedSourceEnvironment, setSelectedSourceEnvironment] = useState("");
|
||||
const [targetAppId, setTargetAppId] = useState("");
|
||||
const [secretPath, setSecretPath] = useState("/");
|
||||
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (shouldLabel) {
|
||||
setValue("labelName", "managed-by");
|
||||
setValue("labelValue", "infisical");
|
||||
return;
|
||||
}
|
||||
|
||||
setValue("labelName", "");
|
||||
setValue("labelValue", "");
|
||||
}, [shouldLabel]);
|
||||
|
||||
useEffect(() => {
|
||||
if (workspace) {
|
||||
setSelectedSourceEnvironment(workspace.environments[0].slug);
|
||||
setValue("selectedSourceEnvironment", workspace.environments[0].slug);
|
||||
}
|
||||
}, [workspace]);
|
||||
|
||||
useEffect(() => {
|
||||
if (integrationAuthApps) {
|
||||
if (integrationAuthApps.length > 0) {
|
||||
setTargetAppId(integrationAuthApps[0].appId as string);
|
||||
setValue("targetAppId", integrationAuthApps[0].appId as string);
|
||||
} else {
|
||||
setTargetAppId("none");
|
||||
setValue("targetAppId", "none");
|
||||
}
|
||||
}
|
||||
}, [integrationAuthApps]);
|
||||
|
||||
const handleButtonClick = async () => {
|
||||
const onFormSubmit = async ({
|
||||
selectedSourceEnvironment: sce,
|
||||
secretPath,
|
||||
targetAppId,
|
||||
secretPrefix,
|
||||
secretSuffix,
|
||||
shouldLabel: sl,
|
||||
labelName,
|
||||
labelValue
|
||||
}: FormData) => {
|
||||
try {
|
||||
setIsLoading(true);
|
||||
|
||||
@@ -66,84 +140,339 @@ export default function GCPSecretManagerCreateIntegrationPage() {
|
||||
isActive: true,
|
||||
app: integrationAuthApps?.find((integrationAuthApp) => integrationAuthApp.appId === targetAppId)?.name,
|
||||
appId: targetAppId,
|
||||
sourceEnvironment: selectedSourceEnvironment,
|
||||
secretPath
|
||||
sourceEnvironment: sce,
|
||||
secretPath,
|
||||
metadata: {
|
||||
...(secretPrefix ? { secretPrefix } : {}),
|
||||
...(secretSuffix ? { secretSuffix } : {}),
|
||||
...(sl ? {
|
||||
secretGCPLabel: {
|
||||
labelName,
|
||||
labelValue
|
||||
}
|
||||
} : {})
|
||||
}
|
||||
});
|
||||
|
||||
setIsLoading(false);
|
||||
router.push(`/integrations/${localStorage.getItem("projectData.id")}`);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return integrationAuth &&
|
||||
workspace &&
|
||||
selectedSourceEnvironment &&
|
||||
integrationAuthApps &&
|
||||
targetAppId ? (
|
||||
<div className="flex h-full w-full items-center justify-center">
|
||||
<Card className="max-w-md rounded-md p-8">
|
||||
<CardTitle className="text-center">GCP Secret Manager Integration</CardTitle>
|
||||
<FormControl label="Project Environment" className="mt-4">
|
||||
<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">
|
||||
<Input
|
||||
value={secretPath}
|
||||
onChange={(evt) => setSecretPath(evt.target.value)}
|
||||
placeholder="Provide a path, default is /"
|
||||
/>
|
||||
</FormControl>
|
||||
<FormControl label="GCP Project">
|
||||
<Select
|
||||
value={targetAppId}
|
||||
onValueChange={(val) => setTargetAppId(val)}
|
||||
className="w-full border border-mineshaft-500"
|
||||
isDisabled={integrationAuthApps.length === 0}
|
||||
>
|
||||
{integrationAuthApps.length > 0 ? (
|
||||
integrationAuthApps.map((integrationAuthApp) => (
|
||||
<SelectItem
|
||||
value={integrationAuthApp.appId as string}
|
||||
key={`target-app-${integrationAuthApp.appId}`}
|
||||
>
|
||||
{integrationAuthApp.name}
|
||||
</SelectItem>
|
||||
))
|
||||
) : (
|
||||
<SelectItem value="none" key="target-app-none">
|
||||
No projects found
|
||||
</SelectItem>
|
||||
)}
|
||||
</Select>
|
||||
</FormControl>
|
||||
integrationAuthApps ? (
|
||||
<form
|
||||
onSubmit={handleSubmit((data: FormData) => {
|
||||
if (!data.secretPrefix && !data.secretSuffix && !data.shouldLabel) {
|
||||
handlePopUpOpen("confirmIntegration", data);
|
||||
return;
|
||||
}
|
||||
|
||||
onFormSubmit(data);
|
||||
})}
|
||||
className="flex flex-col h-full w-full items-center justify-center"
|
||||
>
|
||||
<Card className="max-w-lg rounded-md border border-mineshaft-600">
|
||||
<Head>
|
||||
<title>Set Up GCP Secret Manager Integration</title>
|
||||
<link rel='icon' href='/infisical.ico' />
|
||||
</Head>
|
||||
<CardTitle
|
||||
className="text-left px-6 text-xl mb-2"
|
||||
subTitle="Select which environment or folder in Infisical you want to sync to GCP Secret Manager."
|
||||
>
|
||||
<div className="flex flex-row items-center">
|
||||
<div className="inline flex items-center pb-0.5">
|
||||
<Image
|
||||
src="/images/integrations/Google Cloud Platform.png"
|
||||
height={30}
|
||||
width={30}
|
||||
alt="GCP logo"
|
||||
/>
|
||||
</div>
|
||||
<span className="ml-1.5">GCP Secret Manager Integration </span>
|
||||
<Link href="https://infisical.com/docs/integrations/cloud/gcp-secret-manager" 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.Connection} className="px-6">
|
||||
<TabList>
|
||||
<div className="flex flex-row border-b border-mineshaft-600 w-full">
|
||||
<Tab value={TabSections.Connection}>Connection</Tab>
|
||||
<Tab value={TabSections.Options}>Options</Tab>
|
||||
</div>
|
||||
</TabList>
|
||||
<TabPanel value={TabSections.Connection}>
|
||||
<motion.div
|
||||
key="panel-1"
|
||||
transition={{ duration: 0.15 }}
|
||||
initial={{ opacity: 0, translateX: 30 }}
|
||||
animate={{ opacity: 1, translateX: 0 }}
|
||||
exit={{ opacity: 0, translateX: 30 }}
|
||||
>
|
||||
<Controller
|
||||
control={control}
|
||||
name="selectedSourceEnvironment"
|
||||
render={({ field: { onChange, ...field }, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
label="Project Environment"
|
||||
errorText={error?.message}
|
||||
isError={Boolean(error)}
|
||||
>
|
||||
<Select
|
||||
defaultValue={field.value}
|
||||
{...field}
|
||||
onValueChange={(e) => onChange(e)}
|
||||
className="w-full"
|
||||
>
|
||||
{workspace?.environments.map((sourceEnvironment) => (
|
||||
<SelectItem
|
||||
value={sourceEnvironment.slug}
|
||||
key={`source-environment-${sourceEnvironment.slug}`}
|
||||
>
|
||||
{sourceEnvironment.name}
|
||||
</SelectItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
<Controller
|
||||
control={control}
|
||||
defaultValue=""
|
||||
name="secretPath"
|
||||
render={({ field, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
label="Secrets Path"
|
||||
isError={Boolean(error)}
|
||||
errorText={error?.message}
|
||||
>
|
||||
<Input
|
||||
{...field}
|
||||
placeholder="/"
|
||||
/>
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
<Controller
|
||||
control={control}
|
||||
name="targetAppId"
|
||||
render={({ field: { onChange, ...field }, fieldState: { error } }) => {
|
||||
return (
|
||||
<FormControl
|
||||
label="GCP Project"
|
||||
errorText={error?.message}
|
||||
isError={Boolean(error)}
|
||||
>
|
||||
<Select
|
||||
{...field}
|
||||
onValueChange={(e) => {
|
||||
if (e === "") return;
|
||||
onChange(e)
|
||||
}}
|
||||
className="w-full"
|
||||
>
|
||||
{integrationAuthApps.length > 0 ? (
|
||||
integrationAuthApps.map((integrationAuthApp) => (
|
||||
<SelectItem
|
||||
value={String(integrationAuthApp.appId as string)}
|
||||
key={`target-app-${String(integrationAuthApp.appId)}`}
|
||||
>
|
||||
{integrationAuthApp.name}
|
||||
</SelectItem>
|
||||
))
|
||||
) : (
|
||||
<SelectItem value="none" key="target-app-none">
|
||||
No projects found
|
||||
</SelectItem>
|
||||
)}
|
||||
</Select>
|
||||
</FormControl>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
</motion.div>
|
||||
</TabPanel>
|
||||
<TabPanel value={TabSections.Options}>
|
||||
<motion.div
|
||||
key="panel-1"
|
||||
transition={{ duration: 0.15 }}
|
||||
initial={{ opacity: 0, translateX: -30 }}
|
||||
animate={{ opacity: 1, translateX: 0 }}
|
||||
exit={{ opacity: 0, translateX: 30 }}
|
||||
>
|
||||
<Controller
|
||||
control={control}
|
||||
name="secretPrefix"
|
||||
render={({ field, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
label="Secret Prefix"
|
||||
isError={Boolean(error)}
|
||||
errorText={error?.message}
|
||||
>
|
||||
<Input
|
||||
{...field}
|
||||
placeholder="INFISICAL_"
|
||||
/>
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
<Controller
|
||||
control={control}
|
||||
name="secretSuffix"
|
||||
render={({ field, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
label="Secret Suffix"
|
||||
isError={Boolean(error)}
|
||||
errorText={error?.message}
|
||||
>
|
||||
<Input
|
||||
{...field}
|
||||
placeholder="_INFISICAL"
|
||||
/>
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
<div className="mt-8 mb-[2.36rem]">
|
||||
<Controller
|
||||
control={control}
|
||||
name="shouldLabel"
|
||||
render={({ field: { onChange, value } }) => (
|
||||
<Switch
|
||||
id="label-gcp"
|
||||
onCheckedChange={(isChecked) => onChange(isChecked)}
|
||||
isChecked={value}
|
||||
>
|
||||
Label in GCP Secret Manager
|
||||
</Switch>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
{shouldLabel && (
|
||||
<div className="mt-8">
|
||||
<Controller
|
||||
control={control}
|
||||
name="labelName"
|
||||
render={({ field, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
label="Label Name"
|
||||
isError={Boolean(error)}
|
||||
errorText={error?.message}
|
||||
>
|
||||
<Input
|
||||
{...field}
|
||||
placeholder="managed-by"
|
||||
/>
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
<Controller
|
||||
control={control}
|
||||
name="labelValue"
|
||||
render={({ field, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
label="Label Name"
|
||||
isError={Boolean(error)}
|
||||
errorText={error?.message}
|
||||
>
|
||||
<Input
|
||||
{...field}
|
||||
placeholder="infisical"
|
||||
/>
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</motion.div>
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
<Button
|
||||
onClick={handleButtonClick}
|
||||
color="mineshaft"
|
||||
className="mt-4"
|
||||
colorSchema="primary"
|
||||
variant="outline_bg"
|
||||
className="mb-8 ml-auto mr-6 w-min"
|
||||
size="sm"
|
||||
type="submit"
|
||||
isLoading={isLoading}
|
||||
isDisabled={integrationAuthApps.length === 0}
|
||||
>
|
||||
Create Integration
|
||||
</Button>
|
||||
</Card>
|
||||
</div>
|
||||
{/* <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 Tip</span>
|
||||
</div>
|
||||
<span className="text-mineshaft-300 text-sm mt-4">
|
||||
After creating an integration, your secrets will start syncing immediately.
|
||||
|
||||
To avoid overwriting existing secrets in GCP Secret Manager, you may consider adding a secret prefix/suffix and/or enabling labeling in the options tab.
|
||||
</span>
|
||||
</div> */}
|
||||
<Modal
|
||||
isOpen={popUp.confirmIntegration?.isOpen}
|
||||
onOpenChange={(isOpen) => handlePopUpToggle("confirmIntegration", isOpen)}
|
||||
>
|
||||
<ModalContent
|
||||
title="Heads Up"
|
||||
footerContent={
|
||||
<div className="flex items-center space-x-2">
|
||||
<Button onClick={() => onFormSubmit(popUp.confirmIntegration?.data as FormData)}>
|
||||
Continue Anyway
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => handlePopUpClose("confirmIntegration")}
|
||||
variant="outline_bg"
|
||||
colorSchema="secondary"
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<p>
|
||||
You're about to overwrite any existing secrets in GCP Secret Manager.
|
||||
</p>
|
||||
<p className="mt-4">
|
||||
To avoid this behavior, you may consider adding a secret prefix/suffix or enabling labeling in the options tab.
|
||||
</p>
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
</form>
|
||||
) : (
|
||||
<div />
|
||||
<div className="flex justify-center items-center w-full h-full">
|
||||
<Head>
|
||||
<title>Set Up GCP Secret Manager 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:[email protected]"
|
||||
>
|
||||
support@infisical.com
|
||||
</a> if the issue persists.
|
||||
</p>
|
||||
</div>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,146 +0,0 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { useRouter } from "next/router";
|
||||
import queryString from "query-string";
|
||||
|
||||
import {
|
||||
Button,
|
||||
Card,
|
||||
CardTitle,
|
||||
FormControl,
|
||||
Input,
|
||||
Select,
|
||||
SelectItem
|
||||
} from "@app/components/v2";
|
||||
import {
|
||||
useCreateIntegration
|
||||
} from "@app/hooks/api";
|
||||
import { useGetIntegrationAuthApps,useGetIntegrationAuthById } from "@app/hooks/api/integrationAuth";
|
||||
import { useGetWorkspaceById } from "@app/hooks/api/workspace";
|
||||
|
||||
export default function GCPSecretManagerCreateIntegrationPage() {
|
||||
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 { data: integrationAuthApps } = useGetIntegrationAuthApps({
|
||||
integrationAuthId: (integrationAuthId as string) ?? ""
|
||||
});
|
||||
|
||||
const [selectedSourceEnvironment, setSelectedSourceEnvironment] = useState("");
|
||||
const [targetAppId, setTargetAppId] = useState("");
|
||||
const [secretPath, setSecretPath] = useState("/");
|
||||
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (workspace) {
|
||||
setSelectedSourceEnvironment(workspace.environments[0].slug);
|
||||
}
|
||||
}, [workspace]);
|
||||
|
||||
useEffect(() => {
|
||||
if (integrationAuthApps) {
|
||||
if (integrationAuthApps.length > 0) {
|
||||
setTargetAppId(integrationAuthApps[0].appId as string);
|
||||
} else {
|
||||
setTargetAppId("none");
|
||||
}
|
||||
}
|
||||
}, [integrationAuthApps]);
|
||||
|
||||
const handleButtonClick = async () => {
|
||||
try {
|
||||
setIsLoading(true);
|
||||
|
||||
if (!integrationAuth?._id) return;
|
||||
|
||||
await mutateAsync({
|
||||
integrationAuthId: integrationAuth?._id,
|
||||
isActive: true,
|
||||
app: integrationAuthApps?.find((integrationAuthApp) => integrationAuthApp.appId === targetAppId)?.name,
|
||||
appId: targetAppId,
|
||||
sourceEnvironment: selectedSourceEnvironment,
|
||||
secretPath
|
||||
});
|
||||
|
||||
setIsLoading(false);
|
||||
router.push(`/integrations/${localStorage.getItem("projectData.id")}`);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
};
|
||||
|
||||
return integrationAuth &&
|
||||
workspace &&
|
||||
selectedSourceEnvironment &&
|
||||
integrationAuthApps
|
||||
? (
|
||||
<div className="flex h-full w-full items-center justify-center">
|
||||
<Card className="max-w-md rounded-md p-8">
|
||||
<CardTitle className="text-center">GCP Secret Manager Integration</CardTitle>
|
||||
<FormControl label="Project Environment" className="mt-4">
|
||||
<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">
|
||||
<Input
|
||||
value={secretPath}
|
||||
onChange={(evt) => setSecretPath(evt.target.value)}
|
||||
placeholder="Provide a path, default is /"
|
||||
/>
|
||||
</FormControl>
|
||||
<FormControl label="GCP Project">
|
||||
<Select
|
||||
value={targetAppId}
|
||||
onValueChange={(val) => setTargetAppId(val)}
|
||||
className="w-full border border-mineshaft-500"
|
||||
isDisabled={integrationAuthApps.length === 0}
|
||||
>
|
||||
{integrationAuthApps.length > 0 ? (
|
||||
integrationAuthApps.map((integrationAuthApp) => (
|
||||
<SelectItem
|
||||
value={integrationAuthApp.appId as string}
|
||||
key={`target-app-${integrationAuthApp.appId}`}
|
||||
>
|
||||
{integrationAuthApp.name}
|
||||
</SelectItem>
|
||||
))
|
||||
) : (
|
||||
<SelectItem value="none" key="target-app-none">
|
||||
No projects found
|
||||
</SelectItem>
|
||||
)}
|
||||
</Select>
|
||||
</FormControl>
|
||||
<Button
|
||||
onClick={handleButtonClick}
|
||||
color="mineshaft"
|
||||
className="mt-4"
|
||||
isLoading={isLoading}
|
||||
// isDisabled={integrationAuthApps.length === 0}
|
||||
>
|
||||
Create Integration
|
||||
</Button>
|
||||
</Card>
|
||||
</div>
|
||||
) : (
|
||||
<div />
|
||||
);
|
||||
}
|
||||
|
||||
GCPSecretManagerCreateIntegrationPage.requireAuth = true;
|
||||
@@ -1,5 +1,10 @@
|
||||
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, faCircleInfo } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import queryString from "query-string";
|
||||
|
||||
import {
|
||||
@@ -29,7 +34,7 @@ export default function GitHubCreateIntegrationPage() {
|
||||
|
||||
const { data: workspace } = useGetWorkspaceById(localStorage.getItem("projectData.id") ?? "");
|
||||
const { data: integrationAuth } = useGetIntegrationAuthById((integrationAuthId as string) ?? "");
|
||||
const { data: integrationAuthApps } = useGetIntegrationAuthApps({
|
||||
const { data: integrationAuthApps, isLoading: isIntegrationAuthAppsLoading } = useGetIntegrationAuthApps({
|
||||
integrationAuthId: (integrationAuthId as string) ?? ""
|
||||
});
|
||||
|
||||
@@ -88,10 +93,38 @@ export default function GitHubCreateIntegrationPage() {
|
||||
selectedSourceEnvironment &&
|
||||
integrationAuthApps &&
|
||||
targetAppId ? (
|
||||
<div className="flex h-full w-full items-center justify-center">
|
||||
<Card className="max-w-md rounded-md p-8">
|
||||
<CardTitle className="text-center">GitHub Integration</CardTitle>
|
||||
<FormControl label="Project Environment" className="mt-4">
|
||||
<div className="flex flex-col h-full w-full items-center justify-center">
|
||||
<Head>
|
||||
<title>Set Up GitHub 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 environment variables in GitHub."
|
||||
>
|
||||
<div className="flex flex-row items-center">
|
||||
<div className="inline flex items-center bg-mineshaft-200 rounded-full">
|
||||
<Image
|
||||
src="/images/integrations/GitHub.png"
|
||||
height={30}
|
||||
width={30}
|
||||
alt="GitHub logo"
|
||||
/>
|
||||
</div>
|
||||
<span className="ml-2.5">GitHub Integration </span>
|
||||
<Link href="https://infisical.com/docs/integrations/cicd/githubactions" 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="Project Environment" className="px-6">
|
||||
<Select
|
||||
value={selectedSourceEnvironment}
|
||||
onValueChange={(val) => setSelectedSourceEnvironment(val)}
|
||||
@@ -107,14 +140,14 @@ export default function GitHubCreateIntegrationPage() {
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
<FormControl label="Secrets Path">
|
||||
<FormControl label="Secrets Path" className="px-6">
|
||||
<Input
|
||||
value={secretPath}
|
||||
onChange={(evt) => setSecretPath(evt.target.value)}
|
||||
placeholder="Provide a path, default is /"
|
||||
/>
|
||||
</FormControl>
|
||||
<FormControl label="GitHub Repo" className="mt-4">
|
||||
<FormControl label="GitHub Repo" className="px-6">
|
||||
<Select
|
||||
value={targetAppId}
|
||||
onValueChange={(val) => setTargetAppId(val)}
|
||||
@@ -140,16 +173,40 @@ export default function GitHubCreateIntegrationPage() {
|
||||
<Button
|
||||
onClick={handleButtonClick}
|
||||
color="mineshaft"
|
||||
className="mt-4"
|
||||
variant="outline_bg"
|
||||
className="mt-2 mb-6 ml-auto mr-6"
|
||||
isLoading={isLoading}
|
||||
isDisabled={integrationAuthApps.length === 0}
|
||||
>
|
||||
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 GitHub with secrets from Infisical.</span>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div />
|
||||
<div className="flex justify-center items-center w-full h-full">
|
||||
<Head>
|
||||
<title>Set Up GitHub 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:[email protected]"
|
||||
>
|
||||
support@infisical.com
|
||||
</a> if the issue persists.
|
||||
</p>
|
||||
</div>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,52 +1,118 @@
|
||||
import crypto from "crypto";
|
||||
|
||||
import { useState } from "react";
|
||||
import { faGoogle } from "@fortawesome/free-brands-svg-icons";
|
||||
import { Controller, useForm } from "react-hook-form";
|
||||
import Head from "next/head";
|
||||
import Image from "next/image";
|
||||
import Link from "next/link";
|
||||
import { faArrowUpRightFromSquare, faBookOpen } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { yupResolver } from "@hookform/resolvers/yup";
|
||||
import * as yup from "yup";
|
||||
|
||||
import { useGetCloudIntegrations } from "@app/hooks/api";
|
||||
|
||||
import { Button, Card, CardTitle, FormControl, Input } from "../../../components/v2";
|
||||
|
||||
const schema = yup.object({
|
||||
gitLabURL: yup.string()
|
||||
});
|
||||
|
||||
type FormData = yup.InferType<typeof schema>;
|
||||
|
||||
export default function GitLabAuthorizeIntegrationPage() {
|
||||
const { data: cloudIntegrations } = useGetCloudIntegrations();
|
||||
|
||||
const [gitLabURL, setGitLabURL] = useState("");
|
||||
|
||||
const handleIntegrateWithOAuth = () => {
|
||||
if (!cloudIntegrations) return;
|
||||
const integrationOption = cloudIntegrations.find((integration) => integration.slug === "gitlab");
|
||||
|
||||
if (!integrationOption) return;
|
||||
|
||||
const baseURL = gitLabURL.trim() === "" ? "https://gitlab.com" : gitLabURL.trim();
|
||||
|
||||
const csrfToken = crypto.randomBytes(16).toString("hex");
|
||||
localStorage.setItem("latestCSRFToken", csrfToken);
|
||||
|
||||
const state = `${csrfToken}|${gitLabURL.trim() === "" ? "" : gitLabURL.trim()}`;
|
||||
const link = `${baseURL}/oauth/authorize?client_id=${integrationOption.clientId}&redirect_uri=${window.location.origin}/integrations/gitlab/oauth2/callback&response_type=code&state=${state}`;
|
||||
|
||||
window.location.assign(link);
|
||||
const {
|
||||
control,
|
||||
handleSubmit
|
||||
} = useForm<FormData>({
|
||||
resolver: yupResolver(schema),
|
||||
defaultValues: {
|
||||
gitLabURL: ""
|
||||
}
|
||||
});
|
||||
|
||||
const { data: cloudIntegrations } = useGetCloudIntegrations();
|
||||
|
||||
const onFormSubmit = ({
|
||||
gitLabURL
|
||||
}: FormData) => {
|
||||
if (!cloudIntegrations) return;
|
||||
const integrationOption = cloudIntegrations.find((integration) => integration.slug === "gitlab");
|
||||
|
||||
if (!integrationOption) return;
|
||||
|
||||
const baseURL = (gitLabURL as string).trim() === "" ? "https://gitlab.com" : (gitLabURL as string).trim();
|
||||
|
||||
const csrfToken = crypto.randomBytes(16).toString("hex");
|
||||
localStorage.setItem("latestCSRFToken", csrfToken);
|
||||
|
||||
const state = `${csrfToken}|${(gitLabURL as string).trim() === "" ? "" : (gitLabURL as string).trim()}`;
|
||||
const link = `${baseURL}/oauth/authorize?client_id=${integrationOption.clientId}&redirect_uri=${window.location.origin}/integrations/gitlab/oauth2/callback&response_type=code&state=${state}`;
|
||||
|
||||
window.location.assign(link);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex h-full w-full items-center justify-center">
|
||||
<Card className="max-w-md rounded-md p-8">
|
||||
<CardTitle className="text-center">GitLab Integration</CardTitle>
|
||||
<FormControl label="Self-hosted URL (optional)">
|
||||
<Input
|
||||
placeholder="https://self-hosted-gitlab.com"
|
||||
value={gitLabURL} onChange={(e) => setGitLabURL(e.target.value)}
|
||||
/>
|
||||
</FormControl>
|
||||
<Button
|
||||
onClick={handleIntegrateWithOAuth}
|
||||
leftIcon={<FontAwesomeIcon icon={faGoogle} className="mr-2" />}
|
||||
className="h-11 w-full mx-0 mt-4"
|
||||
<Head>
|
||||
<title>Authorize GitLab 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="Authorize this integration to sync secrets from Infisical to GitLab. If no self-hosted GitLab URL is specified, then Infisical will connect you to GitLab Cloud."
|
||||
>
|
||||
<div className="flex flex-row items-center">
|
||||
<div className="inline flex items-center pb-0.5">
|
||||
<Image
|
||||
src="/images/integrations/Gitlab.png"
|
||||
height={28}
|
||||
width={28}
|
||||
alt="Gitlab logo"
|
||||
/>
|
||||
</div>
|
||||
<span className="ml-2.5">GitLab Integration </span>
|
||||
<Link href="https://infisical.com/docs/integrations/cicd/gitlab" 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>
|
||||
<form
|
||||
onSubmit={handleSubmit(onFormSubmit)}
|
||||
className="px-6 text-right pb-8"
|
||||
>
|
||||
<Controller
|
||||
control={control}
|
||||
name="gitLabURL"
|
||||
render={({ field, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
label="Self-hosted URL (optional)"
|
||||
errorText={error?.message}
|
||||
isError={Boolean(error)}
|
||||
>
|
||||
<Input
|
||||
{...field}
|
||||
placeholder="https://self-hosted-gitlab.com"
|
||||
/>
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
<Button
|
||||
colorSchema="primary"
|
||||
variant="outline_bg"
|
||||
className="mt-2 w-min"
|
||||
size="sm"
|
||||
type="submit"
|
||||
>
|
||||
Continue with OAuth
|
||||
</Button>
|
||||
</Button>
|
||||
</form>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,7 +1,17 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { Controller, useForm } from "react-hook-form";
|
||||
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 { yupResolver } from "@hookform/resolvers/yup";
|
||||
import { motion } from "framer-motion";
|
||||
import queryString from "query-string";
|
||||
import * as yup from "yup";
|
||||
|
||||
import { usePopUp } from "@app/hooks";
|
||||
import {
|
||||
useCreateIntegration
|
||||
} from "@app/hooks/api";
|
||||
@@ -12,8 +22,14 @@ import {
|
||||
CardTitle,
|
||||
FormControl,
|
||||
Input,
|
||||
Modal,
|
||||
ModalContent,
|
||||
Select,
|
||||
SelectItem
|
||||
SelectItem,
|
||||
Tab,
|
||||
TabList,
|
||||
TabPanel,
|
||||
Tabs
|
||||
} from "../../../components/v2";
|
||||
import {
|
||||
useGetIntegrationAuthApps,
|
||||
@@ -27,8 +43,48 @@ const gitLabEntities = [
|
||||
{ name: "Group", value: "group" }
|
||||
];
|
||||
|
||||
enum TabSections {
|
||||
Connection = "connection",
|
||||
Options = "options"
|
||||
}
|
||||
|
||||
const schema = yup.object({
|
||||
targetEntity: yup.string().oneOf(gitLabEntities.map(entity => entity.value), "Invalid entity type"),
|
||||
targetTeamId: yup.string(),
|
||||
selectedSourceEnvironment: yup.string().required("Source environment is required"),
|
||||
secretPath: yup.string().required("Secret path is required"),
|
||||
targetAppId: yup.string().required("GitLab project is required"),
|
||||
targetEnvironment: yup.string(),
|
||||
secretPrefix: yup.string(),
|
||||
secretSuffix: yup.string()
|
||||
});
|
||||
|
||||
type FormData = yup.InferType<typeof schema>;
|
||||
|
||||
export default function GitLabCreateIntegrationPage() {
|
||||
const router = useRouter();
|
||||
const { popUp, handlePopUpOpen, handlePopUpToggle, handlePopUpClose } = usePopUp([
|
||||
"confirmIntegration"
|
||||
] as const);
|
||||
|
||||
const {
|
||||
control,
|
||||
handleSubmit,
|
||||
setValue,
|
||||
watch
|
||||
} = useForm<FormData>({
|
||||
resolver: yupResolver(schema),
|
||||
defaultValues: {
|
||||
targetEntity: "individual",
|
||||
secretPath: "/",
|
||||
secretPrefix: "",
|
||||
secretSuffix: ""
|
||||
}
|
||||
});
|
||||
const selectedSourceEnvironment = watch("selectedSourceEnvironment");
|
||||
const targetEntity = watch("targetEntity");
|
||||
const targetTeamId = watch("targetTeamId");
|
||||
|
||||
const { mutateAsync } = useCreateIntegration();
|
||||
|
||||
const { integrationAuthId } = queryString.parse(router.asPath.split("?")[1]);
|
||||
@@ -36,36 +92,29 @@ export default function GitLabCreateIntegrationPage() {
|
||||
const { data: workspace } = useGetWorkspaceById(localStorage.getItem("projectData.id") ?? "");
|
||||
const { data: integrationAuth } = useGetIntegrationAuthById((integrationAuthId as string) ?? "");
|
||||
|
||||
const [targetTeamId, setTargetTeamId] = useState<string | null>(null);
|
||||
const { data: integrationAuthApps, isLoading: isIntegrationAuthAppsLoading } = useGetIntegrationAuthApps({
|
||||
|
||||
const { data: integrationAuthApps } = useGetIntegrationAuthApps({
|
||||
integrationAuthId: (integrationAuthId as string) ?? "",
|
||||
...(targetTeamId ? { teamId: targetTeamId } : {})
|
||||
});
|
||||
const { data: integrationAuthTeams } = useGetIntegrationAuthTeams(
|
||||
const { data: integrationAuthTeams, isLoading: isintegrationAuthTeamsLoading } = useGetIntegrationAuthTeams(
|
||||
(integrationAuthId as string) ?? ""
|
||||
);
|
||||
|
||||
const [targetEntity, setTargetEntity] = useState(gitLabEntities[0].value);
|
||||
const [selectedSourceEnvironment, setSelectedSourceEnvironment] = useState("");
|
||||
const [secretPath, setSecretPath] = useState("/");
|
||||
const [targetAppId, setTargetAppId] = useState("");
|
||||
const [targetEnvironment, setTargetEnvironment] = useState("");
|
||||
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (workspace) {
|
||||
setSelectedSourceEnvironment(workspace.environments[0].slug);
|
||||
setValue("selectedSourceEnvironment", workspace.environments[0].slug);
|
||||
}
|
||||
}, [workspace]);
|
||||
|
||||
useEffect(() => {
|
||||
if (integrationAuthApps) {
|
||||
if (integrationAuthApps.length > 0) {
|
||||
setTargetAppId(integrationAuthApps[0].appId as string);
|
||||
setValue("targetAppId", String(integrationAuthApps[0].appId as string));
|
||||
} else {
|
||||
setTargetAppId("none");
|
||||
setValue("targetAppId", "none");
|
||||
}
|
||||
}
|
||||
}, [integrationAuthApps]);
|
||||
@@ -75,18 +124,25 @@ export default function GitLabCreateIntegrationPage() {
|
||||
if (integrationAuthTeams) {
|
||||
if (integrationAuthTeams.length > 0) {
|
||||
// case: user is part of at least 1 group in GitLab
|
||||
setTargetTeamId(integrationAuthTeams[0].teamId);
|
||||
setValue("targetTeamId", String(integrationAuthTeams[0].teamId));
|
||||
} else {
|
||||
// case: user is not part of any groups in GitLab
|
||||
setTargetTeamId("none");
|
||||
setValue("targetTeamId", "none");
|
||||
}
|
||||
}
|
||||
} else if (targetEntity === "individual") {
|
||||
setTargetTeamId(null);
|
||||
setValue("targetTeamId", undefined);
|
||||
}
|
||||
}, [targetEntity, integrationAuthTeams]);
|
||||
|
||||
const handleButtonClick = async () => {
|
||||
const onFormSubmit = async ({
|
||||
selectedSourceEnvironment: sse,
|
||||
secretPath,
|
||||
targetAppId,
|
||||
targetEnvironment,
|
||||
secretPrefix,
|
||||
secretSuffix
|
||||
}: FormData) => {
|
||||
try {
|
||||
setIsLoading(true);
|
||||
if (!integrationAuth?._id) return;
|
||||
@@ -94,134 +150,355 @@ export default function GitLabCreateIntegrationPage() {
|
||||
await mutateAsync({
|
||||
integrationAuthId: integrationAuth?._id,
|
||||
isActive: true,
|
||||
app: integrationAuthApps?.find((integrationAuthApp) => integrationAuthApp.appId === targetAppId)?.name,
|
||||
app: integrationAuthApps?.find((integrationAuthApp) => String(integrationAuthApp.appId) === targetAppId)?.name,
|
||||
appId: String(targetAppId),
|
||||
sourceEnvironment: selectedSourceEnvironment,
|
||||
sourceEnvironment: sse,
|
||||
targetEnvironment: targetEnvironment === "" ? "*" : targetEnvironment,
|
||||
secretPath
|
||||
secretPath,
|
||||
metadata: {
|
||||
secretPrefix,
|
||||
secretSuffix
|
||||
}
|
||||
});
|
||||
|
||||
setIsLoading(false);
|
||||
router.push(`/integrations/${localStorage.getItem("projectData.id")}`);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return integrationAuth &&
|
||||
workspace &&
|
||||
selectedSourceEnvironment &&
|
||||
integrationAuthApps &&
|
||||
integrationAuthTeams &&
|
||||
targetAppId ? (
|
||||
<div className="flex h-full w-full items-center justify-center">
|
||||
<Card className="max-w-md rounded-md p-8">
|
||||
<CardTitle className="text-center">GitLab Integration</CardTitle>
|
||||
<FormControl label="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">
|
||||
<Input
|
||||
value={secretPath}
|
||||
onChange={(evt) => setSecretPath(evt.target.value)}
|
||||
placeholder="Provide a path, default is /"
|
||||
/>
|
||||
</FormControl>
|
||||
<FormControl label="GitLab Integration Type">
|
||||
<Select
|
||||
value={targetEntity}
|
||||
onValueChange={(val) => setTargetEntity(val)}
|
||||
className="w-full border border-mineshaft-500"
|
||||
>
|
||||
{gitLabEntities.map((entity) => {
|
||||
return (
|
||||
<SelectItem value={entity.value} key={`target-entity-${entity.value}`}>
|
||||
{entity.name}
|
||||
</SelectItem>
|
||||
);
|
||||
})}
|
||||
</Select>
|
||||
</FormControl>
|
||||
{targetEntity === "group" && targetTeamId && (
|
||||
<FormControl label="GitLab Group">
|
||||
<Select
|
||||
value={targetTeamId}
|
||||
onValueChange={(val) => setTargetTeamId(val)}
|
||||
className="w-full border border-mineshaft-500"
|
||||
integrationAuthTeams ? (
|
||||
<form
|
||||
onSubmit={handleSubmit((data: FormData) => {
|
||||
if (!data.secretPrefix && !data.secretSuffix) {
|
||||
handlePopUpOpen("confirmIntegration", data);
|
||||
return;
|
||||
}
|
||||
|
||||
onFormSubmit(data);
|
||||
})}
|
||||
className="flex flex-col h-full w-full items-center justify-center"
|
||||
>
|
||||
<Head>
|
||||
<title>Set Up GitLab Integration</title>
|
||||
<link rel='icon' href='/infisical.ico' />
|
||||
</Head>
|
||||
<Card className="max-w-lg rounded-md border border-mineshaft-600">
|
||||
<CardTitle
|
||||
className="text-left px-6 text-xl"
|
||||
subTitle="Select which environment or folder in Infisical you want to sync to GitLab's environment variables."
|
||||
>
|
||||
<div className="flex flex-row items-center">
|
||||
<div className="inline flex items-center pb-0.5">
|
||||
<Image
|
||||
src="/images/integrations/Gitlab.png"
|
||||
height={28}
|
||||
width={28}
|
||||
alt="Gitlab logo"
|
||||
/>
|
||||
</div>
|
||||
<span className="ml-2.5">GitLab Integration </span>
|
||||
<Link href="https://infisical.com/docs/integrations/cicd/gitlab" 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.Connection} className="px-6">
|
||||
<TabList>
|
||||
<div className="flex flex-row border-b border-mineshaft-600 w-full">
|
||||
<Tab value={TabSections.Connection}>Connection</Tab>
|
||||
<Tab value={TabSections.Options}>Options</Tab>
|
||||
</div>
|
||||
</TabList>
|
||||
<TabPanel value={TabSections.Connection}>
|
||||
<motion.div
|
||||
key="panel-1"
|
||||
transition={{ duration: 0.15 }}
|
||||
initial={{ opacity: 0, translateX: 30 }}
|
||||
animate={{ opacity: 1, translateX: 0 }}
|
||||
exit={{ opacity: 0, translateX: 30 }}
|
||||
>
|
||||
{integrationAuthTeams.length > 0 ? (
|
||||
integrationAuthTeams.map((integrationAuthTeam) => (
|
||||
<SelectItem
|
||||
value={integrationAuthTeam.teamId}
|
||||
key={`target-team-${integrationAuthTeam.teamId}`}
|
||||
<Controller
|
||||
control={control}
|
||||
name="selectedSourceEnvironment"
|
||||
render={({ field: { onChange, ...field }, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
label="Project Environment"
|
||||
errorText={error?.message}
|
||||
isError={Boolean(error)}
|
||||
>
|
||||
{integrationAuthTeam.name}
|
||||
</SelectItem>
|
||||
))
|
||||
) : (
|
||||
<SelectItem value="none" key="target-team-none">
|
||||
No groups found
|
||||
</SelectItem>
|
||||
<Select
|
||||
defaultValue={field.value}
|
||||
{...field}
|
||||
onValueChange={(e) => onChange(e)}
|
||||
className="w-full"
|
||||
>
|
||||
{workspace?.environments.map((sourceEnvironment) => (
|
||||
<SelectItem
|
||||
value={sourceEnvironment.slug}
|
||||
key={`source-environment-${sourceEnvironment.slug}`}
|
||||
>
|
||||
{sourceEnvironment.name}
|
||||
</SelectItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
<Controller
|
||||
control={control}
|
||||
defaultValue=""
|
||||
name="secretPath"
|
||||
render={({ field, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
label="Secrets Path"
|
||||
isError={Boolean(error)}
|
||||
errorText={error?.message}
|
||||
>
|
||||
<Input
|
||||
{...field}
|
||||
placeholder="/"
|
||||
/>
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
<Controller
|
||||
control={control}
|
||||
name="targetEntity"
|
||||
render={({ field: { onChange, ...field }, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
label="GitLab Integration Type"
|
||||
errorText={error?.message}
|
||||
isError={Boolean(error)}
|
||||
>
|
||||
<Select
|
||||
{...field}
|
||||
onValueChange={(e) => onChange(e)}
|
||||
className="w-full"
|
||||
>
|
||||
{gitLabEntities.map((entity) => {
|
||||
return (
|
||||
<SelectItem value={entity.value} key={`target-entity-${entity.value}`}>
|
||||
{entity.name}
|
||||
</SelectItem>
|
||||
);
|
||||
})}
|
||||
</Select>
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
{targetEntity === "group" && targetTeamId && (
|
||||
<Controller
|
||||
control={control}
|
||||
name="targetTeamId"
|
||||
render={({ field: { onChange, ...field }, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
label="GitLab Group"
|
||||
errorText={error?.message}
|
||||
isError={Boolean(error)}
|
||||
>
|
||||
<Select
|
||||
{...field}
|
||||
onValueChange={(e) => onChange(e)}
|
||||
className="w-full"
|
||||
>
|
||||
{integrationAuthTeams.length > 0 ? (
|
||||
integrationAuthTeams.map((integrationAuthTeam) =>
|
||||
(
|
||||
<SelectItem
|
||||
value={String(integrationAuthTeam.teamId as string)}
|
||||
key={`target-team-${String(integrationAuthTeam.teamId)}`}
|
||||
>
|
||||
{integrationAuthTeam.name}
|
||||
</SelectItem>
|
||||
))
|
||||
) : (
|
||||
<SelectItem value="none" key="target-team-none">
|
||||
No groups found
|
||||
</SelectItem>
|
||||
)}
|
||||
</Select>
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
</Select>
|
||||
</FormControl>
|
||||
)}
|
||||
<FormControl label="GitLab Project">
|
||||
<Select
|
||||
value={targetAppId}
|
||||
onValueChange={(val) => setTargetAppId(val)}
|
||||
className="w-full border border-mineshaft-500"
|
||||
isDisabled={integrationAuthApps.length === 0}
|
||||
>
|
||||
{integrationAuthApps.length > 0 ? (
|
||||
integrationAuthApps.map((integrationAuthApp) => (
|
||||
<SelectItem
|
||||
value={integrationAuthApp.appId as string}
|
||||
key={`target-app-${integrationAuthApp.appId}`}
|
||||
>
|
||||
{integrationAuthApp.name}
|
||||
</SelectItem>
|
||||
))
|
||||
) : (
|
||||
<SelectItem value="none" key="target-app-none">
|
||||
No projects found
|
||||
</SelectItem>
|
||||
)}
|
||||
</Select>
|
||||
</FormControl>
|
||||
<FormControl label="GitLab Environment Scope (Optional)">
|
||||
<Input
|
||||
placeholder="*"
|
||||
value={targetEnvironment}
|
||||
onChange={(e) => setTargetEnvironment(e.target.value)}
|
||||
/>
|
||||
</FormControl>
|
||||
<Controller
|
||||
control={control}
|
||||
name="targetAppId"
|
||||
render={({ field: { onChange, ...field }, fieldState: { error } }) => {
|
||||
return (
|
||||
<FormControl
|
||||
label="GitLab Project"
|
||||
errorText={error?.message}
|
||||
isError={Boolean(error)}
|
||||
>
|
||||
<Select
|
||||
{...field}
|
||||
onValueChange={(e) => {
|
||||
if (e === "") return;
|
||||
onChange(e)
|
||||
}}
|
||||
className="w-full"
|
||||
>
|
||||
{integrationAuthApps.length > 0 ? (
|
||||
integrationAuthApps.map((integrationAuthApp) => (
|
||||
<SelectItem
|
||||
value={String(integrationAuthApp.appId as string)}
|
||||
key={`target-app-${String(integrationAuthApp.appId)}`}
|
||||
>
|
||||
{integrationAuthApp.name}
|
||||
</SelectItem>
|
||||
))
|
||||
) : (
|
||||
<SelectItem value="none" key="target-app-none">
|
||||
No projects found
|
||||
</SelectItem>
|
||||
)}
|
||||
</Select>
|
||||
</FormControl>
|
||||
)}}
|
||||
/>
|
||||
<Controller
|
||||
control={control}
|
||||
defaultValue=""
|
||||
name="targetEnvironment"
|
||||
render={({ field, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
label="GitLab Environment Scope (Optional)"
|
||||
isError={Boolean(error)}
|
||||
errorText={error?.message}
|
||||
>
|
||||
<Input
|
||||
{...field}
|
||||
placeholder="*"
|
||||
/>
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
</motion.div>
|
||||
</TabPanel>
|
||||
<TabPanel value={TabSections.Options}>
|
||||
<motion.div
|
||||
key="panel-1"
|
||||
transition={{ duration: 0.15 }}
|
||||
initial={{ opacity: 0, translateX: -30 }}
|
||||
animate={{ opacity: 1, translateX: 0 }}
|
||||
exit={{ opacity: 0, translateX: 30 }}
|
||||
className="pb-[14.25rem]"
|
||||
>
|
||||
<Controller
|
||||
control={control}
|
||||
name="secretPrefix"
|
||||
render={({ field, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
label="Secret Prefix"
|
||||
isError={Boolean(error)}
|
||||
errorText={error?.message}
|
||||
>
|
||||
<Input
|
||||
{...field}
|
||||
placeholder="INFISICAL_"
|
||||
/>
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
<Controller
|
||||
control={control}
|
||||
name="secretSuffix"
|
||||
render={({ field, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
label="Secret Suffix"
|
||||
isError={Boolean(error)}
|
||||
errorText={error?.message}
|
||||
>
|
||||
<Input
|
||||
{...field}
|
||||
placeholder="_INFISICAL"
|
||||
/>
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
</motion.div>
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
<Button
|
||||
onClick={handleButtonClick}
|
||||
color="mineshaft"
|
||||
className="mt-4"
|
||||
colorSchema="primary"
|
||||
variant="outline_bg"
|
||||
className="mb-8 ml-auto mr-6 w-min"
|
||||
size="sm"
|
||||
type="submit"
|
||||
isLoading={isLoading}
|
||||
isDisabled={integrationAuthApps.length === 0}
|
||||
>
|
||||
Create Integration
|
||||
</Button>
|
||||
</Card>
|
||||
</div>
|
||||
{/* <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 GitLab with secrets from Infisical.</span>
|
||||
</div> */}
|
||||
<Modal
|
||||
isOpen={popUp.confirmIntegration?.isOpen}
|
||||
onOpenChange={(isOpen) => handlePopUpToggle("confirmIntegration", isOpen)}
|
||||
>
|
||||
<ModalContent
|
||||
title="Heads Up"
|
||||
footerContent={
|
||||
<div className="flex items-center space-x-2">
|
||||
<Button onClick={() => onFormSubmit(popUp.confirmIntegration?.data as FormData)}>
|
||||
Continue Anyway
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => handlePopUpClose("confirmIntegration")}
|
||||
variant="outline_bg"
|
||||
colorSchema="secondary"
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<p>
|
||||
You're about to overwrite any existing secrets in GitLab.
|
||||
</p>
|
||||
<p className="mt-4">
|
||||
To avoid this behavior, you may consider adding a secret prefix/suffix in the options tab.
|
||||
</p>
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
</form>
|
||||
) : (
|
||||
<div />
|
||||
<div className="flex justify-center items-center w-full h-full">
|
||||
<Head>
|
||||
<title>Set Up GitLab Integration</title>
|
||||
<link rel='icon' href='/infisical.ico' />
|
||||
</Head>
|
||||
{isIntegrationAuthAppsLoading || isintegrationAuthTeamsLoading ? <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:[email protected]"
|
||||
>
|
||||
support@infisical.com
|
||||
</a> if the issue persists.
|
||||
</p>
|
||||
</div>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
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
|
||||
@@ -80,12 +85,41 @@ export default function HashiCorpVaultAuthorizeIntegrationPage() {
|
||||
|
||||
return (
|
||||
<div className="flex h-full w-full items-center justify-center">
|
||||
<Card className="max-w-md rounded-md p-8">
|
||||
<CardTitle className="text-center">Vault Integration</CardTitle>
|
||||
<Head>
|
||||
<title>Authorize Vault 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 connecting to Vault, 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">
|
||||
<Image
|
||||
src="/images/integrations/Vault.png"
|
||||
height={30}
|
||||
width={30}
|
||||
alt="HCP Vault logo"
|
||||
/>
|
||||
</div>
|
||||
<span className="ml-2.5">HashiCorp Vault Integration</span>
|
||||
<Link href="https://infisical.com/docs/integrations/cloud/hashicorp-vault" 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="Vault Cluster URL"
|
||||
errorText={vaultURLErrorText}
|
||||
isError={vaultURLErrorText !== "" ?? false}
|
||||
className="px-6"
|
||||
>
|
||||
<Input placeholder="" value={vaultURL} onChange={(e) => setVaultURL(e.target.value)} />
|
||||
</FormControl>
|
||||
@@ -93,6 +127,7 @@ export default function HashiCorpVaultAuthorizeIntegrationPage() {
|
||||
label="Vault Namespace"
|
||||
errorText={vaultNamespaceErrorText}
|
||||
isError={vaultNamespaceErrorText !== "" ?? false}
|
||||
className="px-6"
|
||||
>
|
||||
<Input
|
||||
placeholder="admin/education"
|
||||
@@ -104,6 +139,7 @@ export default function HashiCorpVaultAuthorizeIntegrationPage() {
|
||||
label="Vault RoleID"
|
||||
errorText={vaultRoleIDErrorText}
|
||||
isError={vaultRoleIDErrorText !== "" ?? false}
|
||||
className="px-6"
|
||||
>
|
||||
<Input placeholder="" value={vaultRoleID} onChange={(e) => setVaultRoleID(e.target.value)} />
|
||||
</FormControl>
|
||||
@@ -111,6 +147,7 @@ export default function HashiCorpVaultAuthorizeIntegrationPage() {
|
||||
label="Vault SecretID"
|
||||
errorText={vaultSecretIDErrorText}
|
||||
isError={vaultSecretIDErrorText !== "" ?? false}
|
||||
className="px-6"
|
||||
>
|
||||
<Input
|
||||
placeholder=""
|
||||
@@ -120,8 +157,9 @@ export default function HashiCorpVaultAuthorizeIntegrationPage() {
|
||||
</FormControl>
|
||||
<Button
|
||||
onClick={handleButtonClick}
|
||||
color="mineshaft"
|
||||
className="mt-4"
|
||||
colorSchema="primary"
|
||||
variant="outline_bg"
|
||||
className="mb-6 mt-2 ml-auto mr-6 w-min"
|
||||
isLoading={isLoading}
|
||||
>
|
||||
Connect to Vault
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
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, faBugs, faCircleInfo } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import queryString from "query-string";
|
||||
|
||||
import {
|
||||
@@ -25,7 +30,7 @@ export default function HashiCorpVaultCreateIntegrationPage() {
|
||||
const { integrationAuthId } = queryString.parse(router.asPath.split("?")[1]);
|
||||
|
||||
const { data: workspace } = useGetWorkspaceById(localStorage.getItem("projectData.id") ?? "");
|
||||
const { data: integrationAuth } = useGetIntegrationAuthById((integrationAuthId as string) ?? "");
|
||||
const { data: integrationAuth, isLoading: isintegrationAuthLoading } = useGetIntegrationAuthById((integrationAuthId as string) ?? "");
|
||||
|
||||
const [vaultEnginePath, setVaultEnginePath] = useState("");
|
||||
const [vaultEnginePathErrorText, setVaultEnginePathErrorText] = useState("");
|
||||
@@ -79,10 +84,38 @@ export default function HashiCorpVaultCreateIntegrationPage() {
|
||||
};
|
||||
|
||||
return integrationAuth && workspace ? (
|
||||
<div className="flex h-full w-full items-center justify-center">
|
||||
<Card className="max-w-md rounded-md p-8">
|
||||
<CardTitle className="text-center">Vault Integration</CardTitle>
|
||||
<FormControl label="Project Environment" className="mt-4">
|
||||
<div className="flex flex-col h-full w-full items-center justify-center">
|
||||
<Head>
|
||||
<title>Set Up Vault Integration</title>
|
||||
<link rel='icon' href='/infisical.ico' />
|
||||
</Head>
|
||||
<Card className="max-w-lg rounded-md border border-mineshaft-600">
|
||||
<CardTitle
|
||||
className="text-left px-6 text-xl"
|
||||
subTitle="Select which environment or folder in Infisical you want to sync to which path in HashiCorp Vault."
|
||||
>
|
||||
<div className="flex flex-row items-center">
|
||||
<div className="inline flex items-center">
|
||||
<Image
|
||||
src="/images/integrations/Vault.png"
|
||||
height={30}
|
||||
width={30}
|
||||
alt="HCP Vault logo"
|
||||
/>
|
||||
</div>
|
||||
<span className="ml-2.5">HashiCorp Vault Integration</span>
|
||||
<Link href="https://infisical.com/docs/integrations/cloud/hashicorp-vault" 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="Project Environment" className="px-6">
|
||||
<Select
|
||||
value={selectedSourceEnvironment}
|
||||
onValueChange={(val) => setSelectedSourceEnvironment(val)}
|
||||
@@ -98,7 +131,7 @@ export default function HashiCorpVaultCreateIntegrationPage() {
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
<FormControl label="Secrets Path">
|
||||
<FormControl label="Secrets Path" className="px-6">
|
||||
<Input
|
||||
value={secretPath}
|
||||
onChange={(evt) => setSecretPath(evt.target.value)}
|
||||
@@ -109,6 +142,7 @@ export default function HashiCorpVaultCreateIntegrationPage() {
|
||||
label="Vault KV Secrets Engine Path"
|
||||
errorText={vaultEnginePathErrorText}
|
||||
isError={vaultEnginePathErrorText !== "" ?? false}
|
||||
className="px-6"
|
||||
>
|
||||
<Input
|
||||
placeholder="kv"
|
||||
@@ -120,6 +154,7 @@ export default function HashiCorpVaultCreateIntegrationPage() {
|
||||
label="Vault Secret(s) Path"
|
||||
errorText={vaultSecretPathErrorText}
|
||||
isError={vaultSecretPathErrorText !== "" ?? false}
|
||||
className="px-6"
|
||||
>
|
||||
<Input
|
||||
placeholder="machine/dev"
|
||||
@@ -129,17 +164,41 @@ export default function HashiCorpVaultCreateIntegrationPage() {
|
||||
</FormControl>
|
||||
<Button
|
||||
onClick={handleButtonClick}
|
||||
color="mineshaft"
|
||||
className="mt-4"
|
||||
colorSchema="primary"
|
||||
variant="outline_bg"
|
||||
className="mb-6 mt-2 ml-auto mr-6 w-min"
|
||||
isLoading={isLoading}
|
||||
isDisabled={!(isValidVaultPath(vaultEnginePath) && isValidVaultPath(vaultSecretPath))}
|
||||
>
|
||||
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 Vault with secrets from Infisical.</span>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div />
|
||||
<div className="flex justify-center items-center w-full h-full">
|
||||
<Head>
|
||||
<title>Set Up Vault Integration</title>
|
||||
<link rel='icon' href='/infisical.ico' />
|
||||
</Head>
|
||||
{isintegrationAuthLoading ? <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:[email protected]"
|
||||
>
|
||||
support@infisical.com
|
||||
</a> if the issue persists.
|
||||
</p>
|
||||
</div>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
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";
|
||||
|
||||
@@ -39,19 +44,49 @@ export default function RenderCreateIntegrationPage() {
|
||||
|
||||
return (
|
||||
<div className="flex h-full w-full items-center justify-center">
|
||||
<Card className="max-w-md rounded-md p-8">
|
||||
<CardTitle className="text-center">Render Integration</CardTitle>
|
||||
<Head>
|
||||
<title>Authorize Render 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/Render.png"
|
||||
height={30}
|
||||
width={30}
|
||||
alt="Render logo"
|
||||
/>
|
||||
</div>
|
||||
<span className="ml-2.5">Render Integration </span>
|
||||
<Link href="https://infisical.com/docs/integrations/cloud/render" 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="Render API Key"
|
||||
errorText={apiKeyErrorText}
|
||||
isError={apiKeyErrorText !== "" ?? false}
|
||||
className="px-6"
|
||||
>
|
||||
<Input placeholder="rnd_xxx" value={apiKey} onChange={(e) => setApiKey(e.target.value)} />
|
||||
</FormControl>
|
||||
<Button
|
||||
onClick={handleButtonClick}
|
||||
color="mineshaft"
|
||||
className="mt-4"
|
||||
colorSchema="primary"
|
||||
variant="outline_bg"
|
||||
className="mb-6 mt-2 ml-auto mr-6 w-min"
|
||||
isLoading={isLoading}
|
||||
>
|
||||
Connect to Render
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
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, faCircleInfo } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import queryString from "query-string";
|
||||
|
||||
import {
|
||||
@@ -28,8 +33,8 @@ export default function RenderCreateIntegrationPage() {
|
||||
const { integrationAuthId } = queryString.parse(router.asPath.split("?")[1]);
|
||||
|
||||
const { data: workspace } = useGetWorkspaceById(localStorage.getItem("projectData.id") ?? "");
|
||||
const { data: integrationAuth } = useGetIntegrationAuthById((integrationAuthId as string) ?? "");
|
||||
const { data: integrationAuthApps } = useGetIntegrationAuthApps({
|
||||
const { data: integrationAuth, isLoading: isIntegrationAuthLoading } = useGetIntegrationAuthById((integrationAuthId as string) ?? "");
|
||||
const { data: integrationAuthApps, isLoading: isIntegrationAuthAppsLoading } = useGetIntegrationAuthApps({
|
||||
integrationAuthId: (integrationAuthId as string) ?? ""
|
||||
});
|
||||
|
||||
@@ -82,10 +87,38 @@ export default function RenderCreateIntegrationPage() {
|
||||
selectedSourceEnvironment &&
|
||||
integrationAuthApps &&
|
||||
targetApp ? (
|
||||
<div className="flex h-full w-full items-center justify-center">
|
||||
<Card className="max-w-md rounded-md p-8">
|
||||
<CardTitle className="text-center">Render Integration</CardTitle>
|
||||
<FormControl label="Project Environment" className="mt-4">
|
||||
<div className="flex flex-col h-full w-full items-center justify-center">
|
||||
<Head>
|
||||
<title>Set Up Render Integration</title>
|
||||
<link rel='icon' href='/infisical.ico' />
|
||||
</Head>
|
||||
<Card className="max-w-lg rounded-md border border-mineshaft-600">
|
||||
<CardTitle
|
||||
className="text-left px-6 text-xl"
|
||||
subTitle="Choose which environment or folder in Infisical you want to sync to Render environment variables."
|
||||
>
|
||||
<div className="flex flex-row items-center">
|
||||
<div className="inline flex items-center pb-0.5">
|
||||
<Image
|
||||
src="/images/integrations/Render.png"
|
||||
height={30}
|
||||
width={30}
|
||||
alt="Render logo"
|
||||
/>
|
||||
</div>
|
||||
<span className="ml-2.5">Render Integration </span>
|
||||
<Link href="https://infisical.com/docs/integrations/cloud/render" 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="Project Environment" className="px-6">
|
||||
<Select
|
||||
value={selectedSourceEnvironment}
|
||||
onValueChange={(val) => setSelectedSourceEnvironment(val)}
|
||||
@@ -101,14 +134,14 @@ export default function RenderCreateIntegrationPage() {
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
<FormControl label="Secrets Path">
|
||||
<FormControl label="Secrets Path" className="px-6">
|
||||
<Input
|
||||
value={secretPath}
|
||||
onChange={(evt) => setSecretPath(evt.target.value)}
|
||||
placeholder="Provide a path, default is /"
|
||||
/>
|
||||
</FormControl>
|
||||
<FormControl label="Render Service" className="mt-4">
|
||||
<FormControl label="Render Service" className="px-6">
|
||||
<Select
|
||||
value={targetApp}
|
||||
onValueChange={(val) => setTargetApp(val)}
|
||||
@@ -134,16 +167,40 @@ export default function RenderCreateIntegrationPage() {
|
||||
<Button
|
||||
onClick={handleButtonClick}
|
||||
color="mineshaft"
|
||||
className="mt-4"
|
||||
variant="outline_bg"
|
||||
className="mb-6 mt-2 ml-auto mr-6"
|
||||
isLoading={isLoading}
|
||||
isDisabled={integrationAuthApps.length === 0}
|
||||
>
|
||||
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 Render with secrets from Infisical.</span>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div />
|
||||
<div className="flex justify-center items-center w-full h-full">
|
||||
<Head>
|
||||
<title>Set Up Render Integration</title>
|
||||
<link rel='icon' href='/infisical.ico' />
|
||||
</Head>
|
||||
{isIntegrationAuthLoading || 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:[email protected]"
|
||||
>
|
||||
support@infisical.com
|
||||
</a> if the issue persists.
|
||||
</p>
|
||||
</div>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
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
|
||||
@@ -51,12 +56,41 @@ export default function TeamCityCreateIntegrationPage() {
|
||||
|
||||
return (
|
||||
<div className="flex h-full w-full items-center justify-center">
|
||||
<Card className="max-w-md rounded-md p-8">
|
||||
<CardTitle className="text-center">TeamCity Integration</CardTitle>
|
||||
<Head>
|
||||
<title>Authorize TeamCity 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 the details below, 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/TeamCity.png"
|
||||
height={28}
|
||||
width={28}
|
||||
alt="TeamCity logo"
|
||||
/>
|
||||
</div>
|
||||
<span className="ml-2">TeamCity Integration</span>
|
||||
<Link href="https://infisical.com/docs/integrations/cloud/teamcity" 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="TeamCity Access Token"
|
||||
errorText={apiKeyErrorText}
|
||||
isError={apiKeyErrorText !== "" ?? false}
|
||||
className="px-6"
|
||||
>
|
||||
<Input
|
||||
placeholder="Access Token"
|
||||
@@ -68,6 +102,7 @@ export default function TeamCityCreateIntegrationPage() {
|
||||
label="TeamCity Server URL"
|
||||
errorText={serverUrlErrorText}
|
||||
isError={serverUrlErrorText !== "" ?? false}
|
||||
className="px-6"
|
||||
>
|
||||
<Input
|
||||
placeholder="https://example.teamcity.com"
|
||||
@@ -77,8 +112,9 @@ export default function TeamCityCreateIntegrationPage() {
|
||||
</FormControl>
|
||||
<Button
|
||||
onClick={handleButtonClick}
|
||||
color="mineshaft"
|
||||
className="mt-4"
|
||||
colorSchema="primary"
|
||||
variant="outline_bg"
|
||||
className="mb-6 mt-2 ml-auto mr-6 w-min"
|
||||
isLoading={isLoading}
|
||||
>
|
||||
Connect to TeamCity
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
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 queryString from "query-string";
|
||||
|
||||
import {
|
||||
@@ -35,8 +40,8 @@ export default function TeamCityCreateIntegrationPage() {
|
||||
const { integrationAuthId } = queryString.parse(router.asPath.split("?")[1]);
|
||||
|
||||
const { data: workspace } = useGetWorkspaceById(localStorage.getItem("projectData.id") ?? "");
|
||||
const { data: integrationAuth } = useGetIntegrationAuthById((integrationAuthId as string) ?? "");
|
||||
const { data: integrationAuthApps } = useGetIntegrationAuthApps({
|
||||
const { data: integrationAuth, isLoading: isIntegrationAuthLoading } = useGetIntegrationAuthById((integrationAuthId as string) ?? "");
|
||||
const { data: integrationAuthApps, isLoading: isIntegrationAuthAppsLoading } = useGetIntegrationAuthApps({
|
||||
integrationAuthId: (integrationAuthId as string) ?? ""
|
||||
});
|
||||
|
||||
@@ -102,9 +107,37 @@ export default function TeamCityCreateIntegrationPage() {
|
||||
filteredBuildConfigs &&
|
||||
targetAppId ? (
|
||||
<div className="flex h-full w-full items-center justify-center">
|
||||
<Card className="max-w-md rounded-md p-8">
|
||||
<CardTitle className="text-center">TeamCity Integration</CardTitle>
|
||||
<FormControl label="Project Environment" className="mt-4">
|
||||
<Head>
|
||||
<title>Set Up TeamCity 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="Choose which environment or folders in Infisical you want to sync to which project in TeamCity."
|
||||
>
|
||||
<div className="flex flex-row items-center">
|
||||
<div className="inline flex items-center pb-0.5">
|
||||
<Image
|
||||
src="/images/integrations/TeamCity.png"
|
||||
height={28}
|
||||
width={28}
|
||||
alt="TeamCity logo"
|
||||
/>
|
||||
</div>
|
||||
<span className="ml-2">TeamCity Integration</span>
|
||||
<Link href="https://infisical.com/docs/integrations/cloud/teamcity" 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="Project Environment" className="px-6">
|
||||
<Select
|
||||
value={selectedSourceEnvironment}
|
||||
onValueChange={(val) => setSelectedSourceEnvironment(val)}
|
||||
@@ -120,14 +153,14 @@ export default function TeamCityCreateIntegrationPage() {
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
<FormControl label="Secrets Path">
|
||||
<FormControl label="Secrets Path" className="px-6">
|
||||
<Input
|
||||
value={secretPath}
|
||||
onChange={(evt) => setSecretPath(evt.target.value)}
|
||||
placeholder="Provide a path, default is /"
|
||||
/>
|
||||
</FormControl>
|
||||
<FormControl label="TeamCity Project" className="mt-4">
|
||||
<FormControl label="TeamCity Project" className="px-6">
|
||||
<Select
|
||||
value={targetAppId}
|
||||
onValueChange={(val) => setTargetAppId(val)}
|
||||
@@ -150,7 +183,7 @@ export default function TeamCityCreateIntegrationPage() {
|
||||
)}
|
||||
</Select>
|
||||
</FormControl>
|
||||
<FormControl label="Team City Build Config (Optional)" className="mt-4">
|
||||
<FormControl label="Team City Build Config (Optional)" className="px-6">
|
||||
<Select
|
||||
value={targetBuildConfigId}
|
||||
onValueChange={(val) => setTargetBuildConfigId(val)}
|
||||
@@ -169,7 +202,8 @@ export default function TeamCityCreateIntegrationPage() {
|
||||
<Button
|
||||
onClick={handleButtonClick}
|
||||
color="mineshaft"
|
||||
className="mt-4"
|
||||
variant="outline_bg"
|
||||
className="mb-6 mt-2 ml-auto mr-6"
|
||||
isLoading={isLoading}
|
||||
isDisabled={integrationAuthApps.length === 0}
|
||||
>
|
||||
@@ -178,7 +212,25 @@ export default function TeamCityCreateIntegrationPage() {
|
||||
</Card>
|
||||
</div>
|
||||
) : (
|
||||
<div />
|
||||
<div className="flex justify-center items-center w-full h-full">
|
||||
<Head>
|
||||
<title>Set Up TeamCity Integration</title>
|
||||
<link rel='icon' href='/infisical.ico' />
|
||||
</Head>
|
||||
{isIntegrationAuthLoading || 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:[email protected]"
|
||||
>
|
||||
support@infisical.com
|
||||
</a> if the issue persists.
|
||||
</p>
|
||||
</div>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
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, faCircleInfo } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import queryString from "query-string";
|
||||
|
||||
import {
|
||||
@@ -42,7 +47,7 @@ export default function VercelCreateIntegrationPage() {
|
||||
const { integrationAuthId } = queryString.parse(router.asPath.split("?")[1]);
|
||||
const { data: workspace } = useGetWorkspaceById(localStorage.getItem("projectData.id") ?? "");
|
||||
const { data: integrationAuth } = useGetIntegrationAuthById((integrationAuthId as string) ?? "");
|
||||
const { data: integrationAuthApps } = useGetIntegrationAuthApps({
|
||||
const { data: integrationAuthApps, isLoading: isIntegrationAuthAppsLoading } = useGetIntegrationAuthApps({
|
||||
integrationAuthId: (integrationAuthId as string) ?? ""
|
||||
});
|
||||
|
||||
@@ -109,10 +114,38 @@ export default function VercelCreateIntegrationPage() {
|
||||
integrationAuthApps &&
|
||||
targetAppId &&
|
||||
targetEnvironment ? (
|
||||
<div className="flex h-full w-full items-center justify-center">
|
||||
<Card className="max-w-md rounded-md p-8">
|
||||
<CardTitle className="text-center">Vercel Integration</CardTitle>
|
||||
<FormControl label="Project Environment" className="mt-4">
|
||||
<div className="flex flex-col h-full w-full items-center justify-center">
|
||||
<Head>
|
||||
<title>Set Up Vercel 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="Select which environment or folder in Infisical you want to sync to Vercel's environment variables."
|
||||
>
|
||||
<div className="flex flex-row items-center">
|
||||
<div className="inline flex items-center">
|
||||
<Image
|
||||
src="/images/integrations/Vercel.png"
|
||||
height={30}
|
||||
width={30}
|
||||
alt="Vercel logo"
|
||||
/>
|
||||
</div>
|
||||
<span className="ml-2">Vercel Integration </span>
|
||||
<Link href="https://infisical.com/docs/integrations/cloud/vercel" 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="Infisical Project Environment" className="px-6">
|
||||
<Select
|
||||
value={selectedSourceEnvironment}
|
||||
onValueChange={(val) => setSelectedSourceEnvironment(val)}
|
||||
@@ -128,14 +161,14 @@ export default function VercelCreateIntegrationPage() {
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
<FormControl label="Secrets Path">
|
||||
<FormControl label="Secrets Path" className="px-6">
|
||||
<Input
|
||||
value={secretPath}
|
||||
onChange={(evt) => setSecretPath(evt.target.value)}
|
||||
placeholder="Provide a path, default is /"
|
||||
/>
|
||||
</FormControl>
|
||||
<FormControl label="Vercel App">
|
||||
<FormControl label="Vercel App" className="px-6">
|
||||
<Select
|
||||
value={targetAppId}
|
||||
onValueChange={(val) => setTargetAppId(val)}
|
||||
@@ -158,7 +191,7 @@ export default function VercelCreateIntegrationPage() {
|
||||
)}
|
||||
</Select>
|
||||
</FormControl>
|
||||
<FormControl label="Vercel App Environment">
|
||||
<FormControl label="Vercel App Environment" className="px-6">
|
||||
<Select
|
||||
value={targetEnvironment}
|
||||
onValueChange={(val) => setTargetEnvironment(val)}
|
||||
@@ -175,7 +208,7 @@ export default function VercelCreateIntegrationPage() {
|
||||
</Select>
|
||||
</FormControl>
|
||||
{targetEnvironment === "preview" && filteredBranches && (
|
||||
<FormControl label="Vercel Preview Branch (Optional)">
|
||||
<FormControl label="Vercel Preview Branch (Optional)" className="px-6">
|
||||
<Select
|
||||
value={targetBranch}
|
||||
onValueChange={(val) => setTargetBranch(val)}
|
||||
@@ -192,16 +225,40 @@ export default function VercelCreateIntegrationPage() {
|
||||
<Button
|
||||
onClick={handleButtonClick}
|
||||
color="mineshaft"
|
||||
className="mt-4"
|
||||
variant="outline_bg"
|
||||
className="mt-2 mb-6 ml-auto mr-6"
|
||||
isLoading={isLoading}
|
||||
isDisabled={integrationAuthApps.length === 0}
|
||||
>
|
||||
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 Vercel with secrets from Infisical.</span>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div />
|
||||
<div className="flex justify-center items-center w-full h-full">
|
||||
<Head>
|
||||
<title>Set Up Vercel 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:[email protected]"
|
||||
>
|
||||
support@infisical.com
|
||||
</a> if the issue persists.
|
||||
</p>
|
||||
</div>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
|
||||
html {
|
||||
@apply overflow-hidden;
|
||||
}
|
||||
|
||||
.rdp-day,
|
||||
.rdp-nav_button {
|
||||
@apply rounded-md hover:text-mineshaft-500;
|
||||
|
||||
@@ -51,17 +51,20 @@ export const IntegrationsPage = withProjectPermission(
|
||||
const { data: cloudIntegrations, isLoading: isCloudIntegrationsLoading } =
|
||||
useGetCloudIntegrations();
|
||||
|
||||
const { data: integrationAuths, isLoading: isIntegrationAuthLoading } =
|
||||
useGetWorkspaceAuthorizations(
|
||||
workspaceId,
|
||||
useCallback((data: IntegrationAuth[]) => {
|
||||
const groupBy: Record<string, IntegrationAuth> = {};
|
||||
data.forEach((el) => {
|
||||
groupBy[el.integration] = el;
|
||||
});
|
||||
return groupBy;
|
||||
}, [])
|
||||
);
|
||||
const {
|
||||
data: integrationAuths,
|
||||
isLoading: isIntegrationAuthLoading,
|
||||
isFetching: isIntegrationAuthFetching
|
||||
} = useGetWorkspaceAuthorizations(
|
||||
workspaceId,
|
||||
useCallback((data: IntegrationAuth[]) => {
|
||||
const groupBy: Record<string, IntegrationAuth> = {};
|
||||
data.forEach((el) => {
|
||||
groupBy[el.integration] = el;
|
||||
});
|
||||
return groupBy;
|
||||
}, [])
|
||||
);
|
||||
// mutation
|
||||
const {
|
||||
data: integrations,
|
||||
@@ -77,15 +80,23 @@ export const IntegrationsPage = withProjectPermission(
|
||||
const { mutateAsync: deleteIntegration } = useDeleteIntegration();
|
||||
const {
|
||||
mutateAsync: deleteIntegrationAuth,
|
||||
isLoading: isDeleteIntegrationAuthSuccess,
|
||||
isSuccess: isDeleteIntegrationAuthSuccess,
|
||||
reset: resetDeleteIntegrationAuth
|
||||
} = useDeleteIntegrationAuth();
|
||||
|
||||
const isIntegrationsAuthorizedEmpty = !Object.keys(integrationAuths || {}).length;
|
||||
const isIntegrationsEmpty = !integrations?.length;
|
||||
// summary: this use effect is trigger when all integration auths are removed thus deactivate bot
|
||||
// details: so onsuccessfully deleting an integration auth, immediately integration list is refeteched
|
||||
// After the refetch is completed check if its empty. Then set bot active and reset the submit hook
|
||||
// details: so on successfully deleting an integration auth, immediately integration list is refeteched
|
||||
// After the refetch is completed check if its empty. Then set bot active and reset the submit hook for isSuccess to go back to false
|
||||
useEffect(() => {
|
||||
if (isDeleteIntegrationAuthSuccess && !isIntegrationFetching && !integrations?.length) {
|
||||
if (
|
||||
isDeleteIntegrationAuthSuccess &&
|
||||
!isIntegrationFetching &&
|
||||
!isIntegrationAuthFetching &&
|
||||
isIntegrationsAuthorizedEmpty &&
|
||||
isIntegrationsEmpty
|
||||
) {
|
||||
if (bot?._id)
|
||||
updateBotActiveStatusSync({
|
||||
isActive: false,
|
||||
@@ -94,7 +105,13 @@ export const IntegrationsPage = withProjectPermission(
|
||||
});
|
||||
resetDeleteIntegrationAuth();
|
||||
}
|
||||
}, [isIntegrationFetching, isDeleteIntegrationAuthSuccess, integrations?.length]);
|
||||
}, [
|
||||
isIntegrationFetching,
|
||||
isDeleteIntegrationAuthSuccess,
|
||||
isIntegrationAuthFetching,
|
||||
isIntegrationsAuthorizedEmpty,
|
||||
isIntegrationsEmpty
|
||||
]);
|
||||
|
||||
const handleProviderIntegration = async (provider: string) => {
|
||||
const selectedCloudIntegration = cloudIntegrations?.find(({ slug }) => provider === slug);
|
||||
|
||||
-10
@@ -1,4 +1,3 @@
|
||||
import { useState } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import {
|
||||
faArrowLeft,
|
||||
@@ -80,7 +79,6 @@ const SIMPLE_PERMISSION_OPTIONS = [
|
||||
] as const;
|
||||
|
||||
export const OrgRoleModifySection = ({ role, onGoBack }: Props) => {
|
||||
const [searchPermission, setSearchPermission] = useState("");
|
||||
const { subscription } = useSubscription();
|
||||
const { popUp, handlePopUpToggle, handlePopUpOpen } = usePopUp(["upgradePlan"] as const);
|
||||
|
||||
@@ -196,14 +194,6 @@ export const OrgRoleModifySection = ({ role, onGoBack }: Props) => {
|
||||
<div>
|
||||
<h2 className="text-xl font-medium">Add Permission</h2>
|
||||
</div>
|
||||
<div className="flex-1 max-w-md">
|
||||
<Input
|
||||
value={searchPermission}
|
||||
onChange={(e) => setSearchPermission(e.target.value)}
|
||||
leftIcon={<FontAwesomeIcon icon={faMagnifyingGlass} />}
|
||||
placeholder="Search permissions..."
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="">
|
||||
<WorkspacePermission
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { useCallback, useMemo, useState } from "react";
|
||||
import { Controller, useForm } from "react-hook-form";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import Link from "next/link";
|
||||
import { faMagnifyingGlass, faPlus, faTrash, faUsers } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
@@ -359,48 +360,57 @@ export const MemberListTab = ({ roles = [], isRolesLoading }: Props) => {
|
||||
title={t("section.members.add-dialog.add-member-to-project") as string}
|
||||
subTitle={t("section.members.add-dialog.user-will-email")}
|
||||
>
|
||||
<form onSubmit={handleSubmit(onAddMember)}>
|
||||
<Controller
|
||||
control={control}
|
||||
defaultValue={filteredOrgUsers?.[0]?.user?.email}
|
||||
name="email"
|
||||
render={({ field, fieldState: { error } }) => (
|
||||
<FormControl label="Email" isError={Boolean(error)} errorText={error?.message}>
|
||||
<Select
|
||||
position="popper"
|
||||
className="w-full"
|
||||
defaultValue={filteredOrgUsers?.[0]?.user?.email}
|
||||
value={field.value}
|
||||
onValueChange={field.onChange}
|
||||
>
|
||||
{filteredOrgUsers.map(({ _id: orgUserId, user: u }) => (
|
||||
<SelectItem value={u?.email} key={`org-membership-join-${orgUserId}`}>
|
||||
{u?.email}
|
||||
</SelectItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
<div className="mt-8 flex items-center">
|
||||
<Button
|
||||
className="mr-4"
|
||||
size="sm"
|
||||
type="submit"
|
||||
isLoading={isSubmitting}
|
||||
isDisabled={isSubmitting}
|
||||
>
|
||||
Add Member
|
||||
</Button>
|
||||
<Button
|
||||
colorSchema="secondary"
|
||||
variant="plain"
|
||||
onClick={() => handlePopUpClose("addMember")}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
{filteredOrgUsers.length ? (
|
||||
<form onSubmit={handleSubmit(onAddMember)}>
|
||||
<Controller
|
||||
control={control}
|
||||
defaultValue={filteredOrgUsers?.[0]?.user?.email}
|
||||
name="email"
|
||||
render={({ field, fieldState: { error } }) => (
|
||||
<FormControl label="Email" isError={Boolean(error)} errorText={error?.message}>
|
||||
<Select
|
||||
position="popper"
|
||||
className="w-full"
|
||||
defaultValue={filteredOrgUsers?.[0]?.user?.email}
|
||||
value={field.value}
|
||||
onValueChange={field.onChange}
|
||||
>
|
||||
{filteredOrgUsers.map(({ _id: orgUserId, user: u }) => (
|
||||
<SelectItem value={u?.email} key={`org-membership-join-${orgUserId}`}>
|
||||
{u?.email}
|
||||
</SelectItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
<div className="mt-8 flex items-center">
|
||||
<Button
|
||||
className="mr-4"
|
||||
size="sm"
|
||||
type="submit"
|
||||
isLoading={isSubmitting}
|
||||
isDisabled={isSubmitting}
|
||||
>
|
||||
Add Member
|
||||
</Button>
|
||||
<Button
|
||||
colorSchema="secondary"
|
||||
variant="plain"
|
||||
onClick={() => handlePopUpClose("addMember")}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
) : (
|
||||
<div className="flex flex-col space-y-4">
|
||||
<div>All the users in your organization are already invited.</div>
|
||||
<Link href={`/org/${currentWorkspace?.organization}/members`}>
|
||||
<Button variant="outline_bg">Add users to organization</Button>
|
||||
</Link>
|
||||
</div>
|
||||
</form>
|
||||
)}
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
<DeleteActionModal
|
||||
|
||||
-19
@@ -1,14 +1,11 @@
|
||||
import { useState } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { faElementor } from "@fortawesome/free-brands-svg-icons";
|
||||
import {
|
||||
faAnchorLock,
|
||||
faArrowLeft,
|
||||
faBook,
|
||||
faCog,
|
||||
faKey,
|
||||
faLock,
|
||||
faMagnifyingGlass,
|
||||
faNetworkWired,
|
||||
faPuzzlePiece,
|
||||
faTags,
|
||||
@@ -85,12 +82,6 @@ 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",
|
||||
@@ -105,8 +96,6 @@ type Props = {
|
||||
};
|
||||
|
||||
export const ProjectRoleModifySection = ({ role, onGoBack }: Props) => {
|
||||
const [searchPermission, setSearchPermission] = useState("");
|
||||
|
||||
const { popUp, handlePopUpToggle, handlePopUpOpen } = usePopUp(["upgradePlan"] as const);
|
||||
|
||||
const isNonEditable = ["admin", "member", "viewer"].includes(role?.slug || "");
|
||||
@@ -226,14 +215,6 @@ export const ProjectRoleModifySection = ({ role, onGoBack }: Props) => {
|
||||
<div>
|
||||
<h2 className="text-xl font-medium">Add Permission</h2>
|
||||
</div>
|
||||
<div className="max-w-md flex-1">
|
||||
<Input
|
||||
value={searchPermission}
|
||||
onChange={(e) => setSearchPermission(e.target.value)}
|
||||
leftIcon={<FontAwesomeIcon icon={faMagnifyingGlass} />}
|
||||
placeholder="Search permissions..."
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<MultiEnvProjectPermission
|
||||
|
||||
+17
-1
@@ -44,6 +44,22 @@ const PERMISSIONS = [
|
||||
{ action: "delete", label: "Remove" }
|
||||
] as const;
|
||||
|
||||
const MEMBERS_PERMISSIONS = [
|
||||
{ action: "read", label: "View all members" },
|
||||
{ action: "create", label: "Invite members" },
|
||||
{ action: "edit", label: "Edit members" },
|
||||
{ action: "delete", label: "Remove members" }
|
||||
] as const;
|
||||
|
||||
const getPermissionList = (option: Props["formName"]) => {
|
||||
switch (option) {
|
||||
case "member":
|
||||
return MEMBERS_PERMISSIONS;
|
||||
default:
|
||||
return PERMISSIONS;
|
||||
}
|
||||
};
|
||||
|
||||
export const SingleProjectPermission = ({
|
||||
isNonEditable,
|
||||
setValue,
|
||||
@@ -148,7 +164,7 @@ export const SingleProjectPermission = ({
|
||||
className="overflow-hidden grid gap-8 grid-flow-col auto-cols-min"
|
||||
>
|
||||
{isCustom &&
|
||||
PERMISSIONS.map(({ action, label }) => (
|
||||
getPermissionList(formName).map(({ action, label }) => (
|
||||
<Controller
|
||||
name={`permissions.${formName}.${action}`}
|
||||
key={`permissions.${formName}.${action}`}
|
||||
|
||||
+2
-2
@@ -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) => {
|
||||
|
||||
@@ -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.2
|
||||
version: 0.3.3
|
||||
|
||||
# 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
|
||||
|
||||
@@ -72,6 +72,8 @@ kubectl get secrets -n <namespace> <secret-name> \
|
||||
| `frontend.image.repository` | Backend image repository | `infisical/frontend` |
|
||||
| `frontend.image.tag` | Backend image tag | `latest` |
|
||||
| `frontend.image.pullPolicy` | Backend image pullPolicy | `IfNotPresent` |
|
||||
| `frontend.resources.limits.memory` | container memory limit [check the offical kubernetes documentations](https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/) | `100Mi` |
|
||||
| `frontend.resources.requests.cpu` | container CPU request [check the offical kubernetes documentations](https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/) | `10m` |
|
||||
| `frontend.kubeSecretRef` | Backend secret resource reference name (containing required [frontend configuration variables](https://infisical.com/docs/self-hosting/configuration/envars)) | `""` |
|
||||
| `frontend.service.annotations` | Backend service annotations | `{}` |
|
||||
| `frontend.service.type` | Backend service type | `ClusterIP` |
|
||||
@@ -91,6 +93,8 @@ kubectl get secrets -n <namespace> <secret-name> \
|
||||
| `backend.image.repository` | Backend image repository | `infisical/backend` |
|
||||
| `backend.image.tag` | Backend image tag | `latest` |
|
||||
| `backend.image.pullPolicy` | Backend image pullPolicy | `IfNotPresent` |
|
||||
| `backend.resources.limits.memory` | container memory limit [check the offical kubernetes documentations](https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/) | `200Mi` |
|
||||
| `backend.resources.requests.cpu` | container CPU request [check the offical kubernetes documentations](https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/) | `150m` |
|
||||
| `backend.kubeSecretRef` | Backend secret resource reference name (containing required [backend configuration variables](https://infisical.com/docs/self-hosting/configuration/envars)) | `""` |
|
||||
| `backend.service.annotations` | Backend service annotations | `{}` |
|
||||
| `backend.service.type` | Backend service type | `ClusterIP` |
|
||||
@@ -147,6 +151,7 @@ kubectl get secrets -n <namespace> <secret-name> \
|
||||
| `mongodb.auth.databases` | Custom databases list ([special characters warning](https://www.mongodb.com/docs/manual/reference/connection-string/#standard-connection-string-format)) | `["infisical"]` |
|
||||
| `mongodb.auth.rootUser` | Database root user name | `root` |
|
||||
| `mongodb.auth.rootPassword` | Database root user password | `root` |
|
||||
| `mongodb.auth.existingSecret` | Existing secret with MongoDB(®) credentials (keys: `mongodb-passwords`, `mongodb-root-password`, `mongodb-metrics-password`, `mongodb-replica-set-key`) | `""` |
|
||||
| `mongodb.persistence.enabled` | Enable database persistence | `true` |
|
||||
| `mongodb.persistence.existingClaim` | Existing persistent volume claim name | `""` |
|
||||
| `mongodb.persistence.resourcePolicy` | Keep the persistent volume even on deletion (`keep` or `""`) | `keep` |
|
||||
|
||||
@@ -40,7 +40,9 @@ spec:
|
||||
envFrom:
|
||||
- secretRef:
|
||||
name: {{ $backend.kubeSecretRef | default (include "infisical.backend.fullname" .) }}
|
||||
|
||||
{{- if $backend.resources }}
|
||||
resources: {{- toYaml $backend.resources | nindent 12 }}
|
||||
{{- end }}
|
||||
---
|
||||
|
||||
apiVersion: v1
|
||||
|
||||
@@ -40,7 +40,9 @@ spec:
|
||||
name: {{ $frontend.kubeSecretRef | default (include "infisical.frontend.fullname" .) }}
|
||||
ports:
|
||||
- containerPort: 3000
|
||||
|
||||
{{- if $frontend.resources }}
|
||||
resources: {{- toYaml $frontend.resources | nindent 12 }}
|
||||
{{- end }}
|
||||
---
|
||||
|
||||
apiVersion: v1
|
||||
|
||||
@@ -43,6 +43,14 @@ frontend:
|
||||
## @param frontend.image.pullPolicy Backend image pullPolicy
|
||||
##
|
||||
pullPolicy: IfNotPresent
|
||||
## @param frontend.resources.limits.memory container memory limit [check the offical kubernetes documentations](https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/)
|
||||
## @param frontend.resources.requests.cpu container CPU request [check the offical kubernetes documentations](https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/)
|
||||
##
|
||||
resources:
|
||||
limits:
|
||||
memory: 100Mi
|
||||
requests:
|
||||
cpu: 10m
|
||||
## @param frontend.kubeSecretRef Backend secret resource reference name (containing required [frontend configuration variables](https://infisical.com/docs/self-hosting/configuration/envars))
|
||||
##
|
||||
kubeSecretRef: ""
|
||||
@@ -102,6 +110,14 @@ backend:
|
||||
## @param backend.image.pullPolicy Backend image pullPolicy
|
||||
##
|
||||
pullPolicy: IfNotPresent
|
||||
## @param backend.resources.limits.memory container memory limit [check the offical kubernetes documentations](https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/)
|
||||
## @param backend.resources.requests.cpu container CPU request [check the offical kubernetes documentations](https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/)
|
||||
##
|
||||
resources:
|
||||
limits:
|
||||
memory: 200Mi
|
||||
requests:
|
||||
cpu: 150m
|
||||
## @param backend.kubeSecretRef Backend secret resource reference name (containing required [backend configuration variables](https://infisical.com/docs/self-hosting/configuration/envars))
|
||||
##
|
||||
kubeSecretRef: ""
|
||||
@@ -269,6 +285,12 @@ mongodb:
|
||||
## @param mongodb.auth.rootPassword Database root user password
|
||||
##
|
||||
rootPassword: root
|
||||
|
||||
## @param auth.existingSecret Existing secret with MongoDB(®) credentials (keys: `mongodb-passwords`, `mongodb-root-password`, `mongodb-metrics-password`, `mongodb-replica-set-key`)
|
||||
## NOTE: When it's set the previous parameters are ignored.
|
||||
##
|
||||
existingSecret: ""
|
||||
|
||||
## MongoDB persistence configuration
|
||||
##
|
||||
persistence:
|
||||
|
||||
Reference in New Issue
Block a user