Compare commits

..

21 Commits

Author SHA1 Message Date
f8ad8de4c5 Make progress on restyling 2024-03-15 12:31:59 -07:00
2d3fddd0e9 Merge pull request #1570 from Infisical/daniel/api-endpoint-docs
Feat: Documentation improvements
2024-03-14 16:34:00 -07:00
519b92d592 Update mint.json 2024-03-14 19:05:34 +01:00
c3d5e882f8 Merge pull request #1571 from Infisical/aws-ps-integration
Remove recursive behavior in AWS PS integration
2024-03-14 10:36:16 -07:00
4c354eb3ea Remove recursive behavior in AWS PS integration 2024-03-14 10:32:05 -07:00
97eff2b480 Fix: Moved categories and renamed Libs -> SDKs 2024-03-14 18:04:27 +01:00
c621592807 Add .pem loader to tsup 2024-03-14 09:31:58 -07:00
bd400a6196 Merge pull request #1569 from Infisical/render-integration
Add pagination to getAppsRender
2024-03-14 09:30:42 -07:00
a93c2d9236 Add pagination to getAppsRender 2024-03-14 09:24:04 -07:00
11dfeda501 Fix: No nested groups 2024-03-14 17:13:18 +01:00
70bd64d54b Fix: Shorter sidebar titles 2024-03-14 17:09:45 +01:00
0c88a5466c Feat: Documentation improvements 2024-03-14 17:09:37 +01:00
36266b30d5 Fix: Shorter sidebar title 2024-03-14 17:09:21 +01:00
288577b455 Fix: OpenAPI specification URL 2024-03-14 15:41:32 +01:00
5194be14fd Update mint.json 2024-03-14 15:40:53 +01:00
bab8f95fde Feat: Added descriptions to all public API endpoints 2024-03-14 15:40:38 +01:00
b4f372f883 Fix: Delete folder docs page not rendering 2024-03-14 15:40:08 +01:00
b13365ecf5 Feat: Written API explanations 2024-03-14 15:39:29 +01:00
bb6e09a895 Merge pull request #1568 from Infisical/license-docs
Add simple docs for Infisical EE
2024-03-13 18:07:46 -07:00
715b193a8e Add simple docs for Infisical EE 2024-03-13 18:05:13 -07:00
57be493da8 Merge pull request #1567 from Infisical/offline-license
Add EE license support for air-gapped / offline environments
2024-03-13 17:25:35 -07:00
57 changed files with 1300 additions and 743 deletions

View File

@ -2,6 +2,7 @@ import { z } from "zod";
import { AuditLogsSchema, SecretSnapshotsSchema } from "@app/db/schemas";
import { EventType, UserAgentType } from "@app/ee/services/audit-log/audit-log-types";
import { AUDIT_LOGS, PROJECTS } from "@app/lib/api-docs";
import { removeTrailingSlash } from "@app/lib/fn";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type";
@ -19,13 +20,13 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
}
],
params: z.object({
workspaceId: z.string().trim()
workspaceId: z.string().trim().describe(PROJECTS.GET_SNAPSHOTS.workspaceId)
}),
querystring: z.object({
environment: z.string().trim(),
path: z.string().trim().default("/").transform(removeTrailingSlash),
offset: z.coerce.number().default(0),
limit: z.coerce.number().default(20)
environment: z.string().trim().describe(PROJECTS.GET_SNAPSHOTS.environment),
path: z.string().trim().default("/").transform(removeTrailingSlash).describe(PROJECTS.GET_SNAPSHOTS.path),
offset: z.coerce.number().default(0).describe(PROJECTS.GET_SNAPSHOTS.offset),
limit: z.coerce.number().default(20).describe(PROJECTS.GET_SNAPSHOTS.limit)
}),
response: {
200: z.object({
@ -89,16 +90,16 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
}
],
params: z.object({
workspaceId: z.string().trim()
workspaceId: z.string().trim().describe(AUDIT_LOGS.EXPORT.workspaceId)
}),
querystring: z.object({
eventType: z.nativeEnum(EventType).optional(),
userAgentType: z.nativeEnum(UserAgentType).optional(),
startDate: z.string().datetime().optional(),
endDate: z.string().datetime().optional(),
offset: z.coerce.number().default(0),
limit: z.coerce.number().default(20),
actor: z.string().optional()
eventType: z.nativeEnum(EventType).optional().describe(AUDIT_LOGS.EXPORT.eventType),
userAgentType: z.nativeEnum(UserAgentType).optional().describe(AUDIT_LOGS.EXPORT.userAgentType),
startDate: z.string().datetime().optional().describe(AUDIT_LOGS.EXPORT.startDate),
endDate: z.string().datetime().optional().describe(AUDIT_LOGS.EXPORT.endDate),
offset: z.coerce.number().default(0).describe(AUDIT_LOGS.EXPORT.offset),
limit: z.coerce.number().default(20).describe(AUDIT_LOGS.EXPORT.limit),
actor: z.string().optional().describe(AUDIT_LOGS.EXPORT.actor)
}),
response: {
200: z.object({

View File

@ -1,6 +1,7 @@
import { z } from "zod";
import { SecretSnapshotsSchema, SecretTagsSchema, SecretVersionsSchema } from "@app/db/schemas";
import { PROJECTS } from "@app/lib/api-docs";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type";
@ -65,7 +66,7 @@ export const registerSnapshotRouter = async (server: FastifyZodProvider) => {
}
],
params: z.object({
secretSnapshotId: z.string().trim()
secretSnapshotId: z.string().trim().describe(PROJECTS.ROLLBACK_TO_SNAPSHOT.secretSnapshotId)
}),
response: {
200: z.object({

View File

@ -17,7 +17,7 @@ export const getDefaultOnPremFeatures = () => {
customAlerts: false,
auditLogs: false,
auditLogsRetentionDays: 0,
samlSSO: false,
samlSSO: true,
scim: false,
ldap: false,
status: null,

View File

@ -23,7 +23,7 @@ export const getDefaultOnPremFeatures = (): TFeatureSet => ({
customAlerts: false,
auditLogs: false,
auditLogsRetentionDays: 0,
samlSSO: false,
samlSSO: true,
scim: false,
ldap: false,
status: null,

View File

@ -39,7 +39,7 @@ export type TFeatureSet = {
customAlerts: false;
auditLogs: false;
auditLogsRetentionDays: 0;
samlSSO: false;
samlSSO: true;
scim: false;
ldap: false;
status: null;

View File

@ -0,0 +1,286 @@
export const IDENTITIES = {
CREATE: {
name: "The name of the identity to create.",
organizationId: "The organization ID to which the identity belongs.",
role: "The role of the identity. Possible values are 'no-access', 'member', and 'admin'."
},
UPDATE: {
identityId: "The ID of the identity to update.",
name: "The new name of the identity.",
role: "The new role of the identity."
},
DELETE: {
identityId: "The ID of the identity to delete."
}
} as const;
export const UNIVERSAL_AUTH = {
LOGIN: {
clientId: "Your Machine Identity Client ID.",
clientSecret: "Your Machine Identity Client Secret."
},
ATTACH: {
identityId: "The ID of the identity to attach the configuration onto.",
clientSecretTrustedIps:
"A list of IPs or CIDR ranges that the Client Secret can be used from together with the Client ID to get back an access token. You can use 0.0.0.0/0, to allow usage from any network address.",
accessTokenTrustedIps:
"A list of IPs or CIDR ranges that access tokens can be used from. You can use 0.0.0.0/0, to allow usage from any network address.",
accessTokenTTL: "The lifetime for an access token in seconds. This value will be referenced at renewal time.",
accessTokenMaxTTL:
"The maximum lifetime for an access token in seconds. This value will be referenced at renewal time.",
accessTokenNumUsesLimit:
"The maximum number of times that an access token can be used; a value of 0 implies infinite number of uses."
},
RETRIEVE: {
identityId: "The ID of the identity to retrieve."
},
UPDATE: {
identityId: "The ID of the identity to update.",
clientSecretTrustedIps: "The new list of IPs or CIDR ranges that the Client Secret can be used from.",
accessTokenTrustedIps: "The new list of IPs or CIDR ranges that access tokens can be used from.",
accessTokenTTL: "The new lifetime for an access token in seconds.",
accessTokenMaxTTL: "The new maximum lifetime for an access token in seconds.",
accessTokenNumUsesLimit: "The new maximum number of times that an access token can be used."
},
CREATE_CLIENT_SECRET: {
identityId: "The ID of the identity to create a client secret for.",
description: "The description of the client secret.",
numUsesLimit:
"The maximum number of times that the client secret can be used; a value of 0 implies infinite number of uses.",
ttl: "The lifetime for the client secret in seconds."
},
LIST_CLIENT_SECRETS: {
identityId: "The ID of the identity to list client secrets for."
},
REVOKE_CLIENT_SECRET: {
identityId: "The ID of the identity to revoke the client secret from.",
clientSecretId: "The ID of the client secret to revoke."
},
RENEW_ACCESS_TOKEN: {
accessToken: "The access token to renew."
}
} as const;
export const ORGANIZATIONS = {
LIST_USER_MEMBERSHIPS: {
organizationId: "The ID of the organization to get memberships from."
},
UPDATE_USER_MEMBERSHIP: {
organizationId: "The ID of the organization to update the membership for.",
membershipId: "The ID of the membership to update.",
role: "The new role of the membership."
},
DELETE_USER_MEMBERSHIP: {
organizationId: "The ID of the organization to delete the membership from.",
membershipId: "The ID of the membership to delete."
},
LIST_IDENTITY_MEMBERSHIPS: {
orgId: "The ID of the organization to get identity memberships from."
},
GET_PROJECTS: {
organizationId: "The ID of the organization to get projects from."
}
} as const;
export const PROJECTS = {
CREATE: {
organizationId: "The ID of the organization to create the project in.",
projectName: "The name of the project to create.",
slug: "An optional slug for the project."
},
DELETE: {
workspaceId: "The ID of the project to delete."
},
GET: {
workspaceId: "The ID of the project."
},
UPDATE: {
workspaceId: "The ID of the project to update.",
name: "The new name of the project.",
autoCapitalization: "Disable or enable auto-capitalization for the project."
},
INVITE_MEMBER: {
projectId: "The ID of the project to invite the member to.",
emails: "A list of organization member emails to invite to the project.",
usernames: "A list of usernames to invite to the project."
},
REMOVE_MEMBER: {
projectId: "The ID of the project to remove the member from.",
emails: "A list of organization member emails to remove from the project.",
usernames: "A list of usernames to remove from the project."
},
GET_USER_MEMBERSHIPS: {
workspaceId: "The ID of the project to get memberships from."
},
UPDATE_USER_MEMBERSHIP: {
workspaceId: "The ID of the project to update the membership for.",
membershipId: "The ID of the membership to update.",
roles: "A list of roles to update the membership to."
},
LIST_IDENTITY_MEMBERSHIPS: {
projectId: "The ID of the project to get identity memberships from."
},
UPDATE_IDENTITY_MEMBERSHIP: {
projectId: "The ID of the project to update the identity membership for.",
identityId: "The ID of the identity to update the membership for.",
roles: "A list of roles to update the membership to."
},
DELETE_IDENTITY_MEMBERSHIP: {
projectId: "The ID of the project to delete the identity membership from.",
identityId: "The ID of the identity to delete the membership from."
},
GET_KEY: {
workspaceId: "The ID of the project to get the key from."
},
GET_SNAPSHOTS: {
workspaceId: "The ID of the project to get snapshots from.",
environment: "The environment to get snapshots from.",
path: "The secret path to get snapshots from.",
offset: "The offset to start from. If you enter 10, it will start from the 10th snapshot.",
limit: "The number of snapshots to return."
},
ROLLBACK_TO_SNAPSHOT: {
secretSnapshotId: "The ID of the snapshot to rollback to."
}
} as const;
export const ENVIRONMENTS = {
CREATE: {
workspaceId: "The ID of the project to create the environment in.",
name: "The name of the environment to create.",
slug: "The slug of the environment to create."
},
UPDATE: {
workspaceId: "The ID of the project to update the environment in.",
id: "The ID of the environment to update.",
name: "The new name of the environment.",
slug: "The new slug of the environment.",
position: "The new position of the environment. The lowest number will be displayed as the first environment."
},
DELETE: {
workspaceId: "The ID of the project to delete the environment from.",
id: "The ID of the environment to delete."
}
} as const;
export const FOLDERS = {
LIST: {
workspaceId: "The ID of the project to list folders from.",
environment: "The slug of the environment to list folders from.",
path: "The path to list folders from.",
directory: "The directory to list folders from. (Deprecated in favor of path)"
},
CREATE: {
workspaceId: "The ID of the project to create the folder in.",
environment: "The slug of the environment to create the folder in.",
name: "The name of the folder to create.",
path: "The path of the folder to create.",
directory: "The directory of the folder to create. (Deprecated in favor of path)"
},
UPDATE: {
folderId: "The ID of the folder to update.",
environment: "The slug of the environment where the folder is located.",
name: "The new name of the folder.",
path: "The path of the folder to update.",
directory: "The new directory of the folder to update. (Deprecated in favor of path)",
workspaceId: "The ID of the project where the folder is located."
},
DELETE: {
folderIdOrName: "The ID or name of the folder to delete.",
workspaceId: "The ID of the project to delete the folder from.",
environment: "The slug of the environment where the folder is located.",
directory: "The directory of the folder to delete. (Deprecated in favor of path)",
path: "The path of the folder to delete."
}
} as const;
export const RAW_SECRETS = {
LIST: {
workspaceId: "The ID of the project to list secrets from.",
environment: "The slug of the environment to list secrets from.",
secretPath: "The secret path to list secrets from.",
includeImports: "Weather to include imported secrets or not."
},
CREATE: {
secretName: "The name of the secret to create.",
environment: "The slug of the environment to create the secret in.",
secretComment: "Attach a comment to the secret.",
secretPath: "The path to create the secret in.",
secretValue: "The value of the secret to create.",
skipMultilineEncoding: "Skip multiline encoding for the secret value.",
type: "The type of the secret to create.",
workspaceId: "The ID of the project to create the secret in."
},
GET: {
secretName: "The name of the secret to get.",
workspaceId: "The ID of the project to get the secret from.",
environment: "The slug of the environment to get the secret from.",
secretPath: "The path of the secret to get.",
version: "The version of the secret to get.",
type: "The type of the secret to get.",
includeImports: "Weather to include imported secrets or not."
},
UPDATE: {
secretName: "The name of the secret to update.",
environment: "The slug of the environment where the secret is located.",
secretPath: "The path of the secret to update",
secretValue: "The new value of the secret.",
skipMultilineEncoding: "Skip multiline encoding for the secret value.",
type: "The type of the secret to update.",
workspaceId: "The ID of the project to update the secret in."
},
DELETE: {
secretName: "The name of the secret to delete.",
environment: "The slug of the environment where the secret is located.",
secretPath: "The path of the secret.",
type: "The type of the secret to delete.",
workspaceId: "The ID of the project where the secret is located."
}
} as const;
export const SECRET_IMPORTS = {
LIST: {
workspaceId: "The ID of the project to list secret imports from.",
environment: "The slug of the environment to list secret imports from.",
path: "The path to list secret imports from."
},
CREATE: {
environment: "The slug of the environment to import into.",
path: "The path to import into.",
workspaceId: "The ID of the project you are working in.",
import: {
environment: "The slug of the environment to import from.",
path: "The path to import from."
}
},
UPDATE: {
secretImportId: "The ID of the secret import to update.",
environment: "The slug of the environment where the secret import is located.",
import: {
environment: "The new environment slug to import from.",
path: "The new path to import from.",
position: "The new position of the secret import. The lowest number will be displayed as the first import."
},
path: "The path of the secret import to update.",
workspaceId: "The ID of the project where the secret import is located."
},
DELETE: {
workspaceId: "The ID of the project to delete the secret import from.",
secretImportId: "The ID of the secret import to delete.",
environment: "The slug of the environment where the secret import is located.",
path: "The path of the secret import to delete."
}
} as const;
export const AUDIT_LOGS = {
EXPORT: {
workspaceId: "The ID of the project to export audit logs from.",
eventType: "The type of the event to export.",
userAgentType: "Choose which consuming application to export audit logs for.",
startDate: "The date to start the export from.",
endDate: "The date to end the export at.",
offset: "The offset to start from. If you enter 10, it will start from the 10th audit log.",
limit: "The number of audit logs to return.",
actor: "The actor to filter the audit logs by."
}
} as const;

View File

@ -0,0 +1 @@
export * from "./constants";

View File

@ -1,5 +1,7 @@
import { z } from "zod";
import { UNIVERSAL_AUTH } from "@app/lib/api-docs";
export const registerIdentityAccessTokenRouter = async (server: FastifyZodProvider) => {
server.route({
url: "/token/renew",
@ -7,7 +9,7 @@ export const registerIdentityAccessTokenRouter = async (server: FastifyZodProvid
schema: {
description: "Renew access token",
body: z.object({
accessToken: z.string().trim()
accessToken: z.string().trim().describe(UNIVERSAL_AUTH.RENEW_ACCESS_TOKEN.accessToken)
}),
response: {
200: z.object({

View File

@ -2,6 +2,7 @@ import { z } from "zod";
import { IdentitiesSchema, OrgMembershipRole } from "@app/db/schemas";
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
import { IDENTITIES } from "@app/lib/api-docs";
import { getTelemetryDistinctId } from "@app/server/lib/telemetry";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type";
@ -20,9 +21,9 @@ export const registerIdentityRouter = async (server: FastifyZodProvider) => {
}
],
body: z.object({
name: z.string().trim(),
organizationId: z.string().trim(),
role: z.string().trim().min(1).default(OrgMembershipRole.NoAccess)
name: z.string().trim().describe(IDENTITIES.CREATE.name),
organizationId: z.string().trim().describe(IDENTITIES.CREATE.organizationId),
role: z.string().trim().min(1).default(OrgMembershipRole.NoAccess).describe(IDENTITIES.CREATE.role)
}),
response: {
200: z.object({
@ -78,11 +79,11 @@ export const registerIdentityRouter = async (server: FastifyZodProvider) => {
}
],
params: z.object({
identityId: z.string()
identityId: z.string().describe(IDENTITIES.UPDATE.identityId)
}),
body: z.object({
name: z.string().trim().optional(),
role: z.string().trim().min(1).optional()
name: z.string().trim().optional().describe(IDENTITIES.UPDATE.name),
role: z.string().trim().min(1).optional().describe(IDENTITIES.UPDATE.role)
}),
response: {
200: z.object({
@ -127,7 +128,7 @@ export const registerIdentityRouter = async (server: FastifyZodProvider) => {
}
],
params: z.object({
identityId: z.string()
identityId: z.string().describe(IDENTITIES.DELETE.identityId)
}),
response: {
200: z.object({

View File

@ -2,6 +2,7 @@ import { z } from "zod";
import { IdentityUaClientSecretsSchema, IdentityUniversalAuthsSchema } from "@app/db/schemas";
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
import { UNIVERSAL_AUTH } from "@app/lib/api-docs";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type";
import { TIdentityTrustedIp } from "@app/services/identity/identity-types";
@ -26,8 +27,8 @@ export const registerIdentityUaRouter = async (server: FastifyZodProvider) => {
schema: {
description: "Login with Universal Auth",
body: z.object({
clientId: z.string().trim(),
clientSecret: z.string().trim()
clientId: z.string().trim().describe(UNIVERSAL_AUTH.LOGIN.clientId),
clientSecret: z.string().trim().describe(UNIVERSAL_AUTH.LOGIN.clientSecret)
}),
response: {
200: z.object({
@ -76,7 +77,7 @@ export const registerIdentityUaRouter = async (server: FastifyZodProvider) => {
}
],
params: z.object({
identityId: z.string().trim()
identityId: z.string().trim().describe(UNIVERSAL_AUTH.ATTACH.identityId)
}),
body: z.object({
clientSecretTrustedIps: z
@ -85,14 +86,16 @@ export const registerIdentityUaRouter = async (server: FastifyZodProvider) => {
})
.array()
.min(1)
.default([{ ipAddress: "0.0.0.0/0" }, { ipAddress: "::/0" }]),
.default([{ ipAddress: "0.0.0.0/0" }, { ipAddress: "::/0" }])
.describe(UNIVERSAL_AUTH.ATTACH.clientSecretTrustedIps),
accessTokenTrustedIps: z
.object({
ipAddress: z.string().trim()
})
.array()
.min(1)
.default([{ ipAddress: "0.0.0.0/0" }, { ipAddress: "::/0" }]),
.default([{ ipAddress: "0.0.0.0/0" }, { ipAddress: "::/0" }])
.describe(UNIVERSAL_AUTH.ATTACH.accessTokenTrustedIps),
accessTokenTTL: z
.number()
.int()
@ -100,15 +103,22 @@ export const registerIdentityUaRouter = async (server: FastifyZodProvider) => {
.refine((value) => value !== 0, {
message: "accessTokenTTL must have a non zero number"
})
.default(2592000),
.default(2592000)
.describe(UNIVERSAL_AUTH.ATTACH.accessTokenTTL), // 30 days
accessTokenMaxTTL: z
.number()
.int()
.refine((value) => value !== 0, {
message: "accessTokenMaxTTL must have a non zero number"
})
.default(2592000), // 30 days
accessTokenNumUsesLimit: z.number().int().min(0).default(0)
.default(2592000)
.describe(UNIVERSAL_AUTH.ATTACH.accessTokenMaxTTL), // 30 days
accessTokenNumUsesLimit: z
.number()
.int()
.min(0)
.default(0)
.describe(UNIVERSAL_AUTH.ATTACH.accessTokenNumUsesLimit)
}),
response: {
200: z.object({
@ -156,7 +166,7 @@ export const registerIdentityUaRouter = async (server: FastifyZodProvider) => {
}
],
params: z.object({
identityId: z.string()
identityId: z.string().describe(UNIVERSAL_AUTH.UPDATE.identityId)
}),
body: z.object({
clientSecretTrustedIps: z
@ -165,16 +175,23 @@ export const registerIdentityUaRouter = async (server: FastifyZodProvider) => {
})
.array()
.min(1)
.optional(),
.optional()
.describe(UNIVERSAL_AUTH.UPDATE.clientSecretTrustedIps),
accessTokenTrustedIps: z
.object({
ipAddress: z.string().trim()
})
.array()
.min(1)
.optional(),
accessTokenTTL: z.number().int().min(0).optional(),
accessTokenNumUsesLimit: z.number().int().min(0).optional(),
.optional()
.describe(UNIVERSAL_AUTH.UPDATE.accessTokenTrustedIps),
accessTokenTTL: z.number().int().min(0).optional().describe(UNIVERSAL_AUTH.UPDATE.accessTokenTTL),
accessTokenNumUsesLimit: z
.number()
.int()
.min(0)
.optional()
.describe(UNIVERSAL_AUTH.UPDATE.accessTokenNumUsesLimit),
accessTokenMaxTTL: z
.number()
.int()
@ -182,6 +199,7 @@ export const registerIdentityUaRouter = async (server: FastifyZodProvider) => {
message: "accessTokenMaxTTL must have a non zero number"
})
.optional()
.describe(UNIVERSAL_AUTH.UPDATE.accessTokenMaxTTL)
}),
response: {
200: z.object({
@ -230,7 +248,7 @@ export const registerIdentityUaRouter = async (server: FastifyZodProvider) => {
}
],
params: z.object({
identityId: z.string()
identityId: z.string().describe(UNIVERSAL_AUTH.RETRIEVE.identityId)
}),
response: {
200: z.object({
@ -273,12 +291,12 @@ export const registerIdentityUaRouter = async (server: FastifyZodProvider) => {
}
],
params: z.object({
identityId: z.string()
identityId: z.string().describe(UNIVERSAL_AUTH.CREATE_CLIENT_SECRET.identityId)
}),
body: z.object({
description: z.string().trim().default(""),
numUsesLimit: z.number().min(0).default(0),
ttl: z.number().min(0).default(0)
description: z.string().trim().default("").describe(UNIVERSAL_AUTH.CREATE_CLIENT_SECRET.description),
numUsesLimit: z.number().min(0).default(0).describe(UNIVERSAL_AUTH.CREATE_CLIENT_SECRET.numUsesLimit),
ttl: z.number().min(0).default(0).describe(UNIVERSAL_AUTH.CREATE_CLIENT_SECRET.ttl)
}),
response: {
200: z.object({
@ -324,7 +342,7 @@ export const registerIdentityUaRouter = async (server: FastifyZodProvider) => {
}
],
params: z.object({
identityId: z.string()
identityId: z.string().describe(UNIVERSAL_AUTH.LIST_CLIENT_SECRETS.identityId)
}),
response: {
200: z.object({
@ -366,8 +384,8 @@ export const registerIdentityUaRouter = async (server: FastifyZodProvider) => {
}
],
params: z.object({
identityId: z.string(),
clientSecretId: z.string()
identityId: z.string().describe(UNIVERSAL_AUTH.REVOKE_CLIENT_SECRET.identityId),
clientSecretId: z.string().describe(UNIVERSAL_AUTH.REVOKE_CLIENT_SECRET.clientSecretId)
}),
response: {
200: z.object({

View File

@ -2,6 +2,7 @@ import { z } from "zod";
import { ProjectEnvironmentsSchema } from "@app/db/schemas";
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
import { ENVIRONMENTS } from "@app/lib/api-docs";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type";
@ -18,11 +19,11 @@ export const registerProjectEnvRouter = async (server: FastifyZodProvider) => {
}
],
params: z.object({
workspaceId: z.string().trim()
workspaceId: z.string().trim().describe(ENVIRONMENTS.CREATE.workspaceId)
}),
body: z.object({
name: z.string().trim(),
slug: z.string().trim()
name: z.string().trim().describe(ENVIRONMENTS.CREATE.name),
slug: z.string().trim().describe(ENVIRONMENTS.CREATE.slug)
}),
response: {
200: z.object({
@ -73,13 +74,13 @@ export const registerProjectEnvRouter = async (server: FastifyZodProvider) => {
}
],
params: z.object({
workspaceId: z.string().trim(),
id: z.string().trim()
workspaceId: z.string().trim().describe(ENVIRONMENTS.UPDATE.workspaceId),
id: z.string().trim().describe(ENVIRONMENTS.UPDATE.id)
}),
body: z.object({
slug: z.string().trim().optional(),
name: z.string().trim().optional(),
position: z.number().optional()
slug: z.string().trim().optional().describe(ENVIRONMENTS.UPDATE.slug),
name: z.string().trim().optional().describe(ENVIRONMENTS.UPDATE.name),
position: z.number().optional().describe(ENVIRONMENTS.UPDATE.position)
}),
response: {
200: z.object({
@ -136,8 +137,8 @@ export const registerProjectEnvRouter = async (server: FastifyZodProvider) => {
}
],
params: z.object({
workspaceId: z.string().trim(),
id: z.string().trim()
workspaceId: z.string().trim().describe(ENVIRONMENTS.DELETE.workspaceId),
id: z.string().trim().describe(ENVIRONMENTS.DELETE.id)
}),
response: {
200: z.object({

View File

@ -9,6 +9,7 @@ import {
UsersSchema
} from "@app/db/schemas";
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
import { PROJECTS } from "@app/lib/api-docs";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type";
import { ProjectUserMembershipTemporaryMode } from "@app/services/project-membership/project-membership-types";
@ -26,7 +27,7 @@ export const registerProjectMembershipRouter = async (server: FastifyZodProvider
}
],
params: z.object({
workspaceId: z.string().trim()
workspaceId: z.string().trim().describe(PROJECTS.GET_USER_MEMBERSHIPS.workspaceId)
}),
response: {
200: z.object({
@ -134,8 +135,8 @@ export const registerProjectMembershipRouter = async (server: FastifyZodProvider
}
],
params: z.object({
workspaceId: z.string().trim(),
membershipId: z.string().trim()
workspaceId: z.string().trim().describe(PROJECTS.UPDATE_USER_MEMBERSHIP.workspaceId),
membershipId: z.string().trim().describe(PROJECTS.UPDATE_USER_MEMBERSHIP.membershipId)
}),
body: z.object({
roles: z
@ -156,6 +157,7 @@ export const registerProjectMembershipRouter = async (server: FastifyZodProvider
)
.min(1)
.refine((data) => data.some(({ isTemporary }) => !isTemporary), "At least long lived role is required")
.describe(PROJECTS.UPDATE_USER_MEMBERSHIP.roles)
}),
response: {
200: z.object({

View File

@ -7,6 +7,7 @@ import {
UserEncryptionKeysSchema,
UsersSchema
} from "@app/db/schemas";
import { PROJECTS } from "@app/lib/api-docs";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type";
@ -125,7 +126,7 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
method: "GET",
schema: {
params: z.object({
workspaceId: z.string().trim()
workspaceId: z.string().trim().describe(PROJECTS.GET.workspaceId)
}),
response: {
200: z.object({
@ -177,7 +178,7 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
method: "DELETE",
schema: {
params: z.object({
workspaceId: z.string().trim()
workspaceId: z.string().trim().describe(PROJECTS.DELETE.workspaceId)
}),
response: {
200: z.object({
@ -235,11 +236,16 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
method: "PATCH",
schema: {
params: z.object({
workspaceId: z.string().trim()
workspaceId: z.string().trim().describe(PROJECTS.UPDATE.workspaceId)
}),
body: z.object({
name: z.string().trim().max(64, { message: "Name must be 64 or fewer characters" }).optional(),
autoCapitalization: z.boolean().optional()
name: z
.string()
.trim()
.max(64, { message: "Name must be 64 or fewer characters" })
.optional()
.describe(PROJECTS.UPDATE.name),
autoCapitalization: z.boolean().optional().describe(PROJECTS.UPDATE.autoCapitalization)
}),
response: {
200: z.object({

View File

@ -2,6 +2,7 @@ import { z } from "zod";
import { SecretFoldersSchema } from "@app/db/schemas";
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
import { FOLDERS } from "@app/lib/api-docs";
import { removeTrailingSlash } from "@app/lib/fn";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type";
@ -19,12 +20,12 @@ export const registerSecretFolderRouter = async (server: FastifyZodProvider) =>
}
],
body: z.object({
workspaceId: z.string().trim(),
environment: z.string().trim(),
name: z.string().trim(),
path: z.string().trim().default("/").transform(removeTrailingSlash),
workspaceId: z.string().trim().describe(FOLDERS.CREATE.workspaceId),
environment: z.string().trim().describe(FOLDERS.CREATE.environment),
name: z.string().trim().describe(FOLDERS.CREATE.name),
path: z.string().trim().default("/").transform(removeTrailingSlash).describe(FOLDERS.CREATE.path),
// backward compatiability with cli
directory: z.string().trim().default("/").transform(removeTrailingSlash)
directory: z.string().trim().default("/").transform(removeTrailingSlash).describe(FOLDERS.CREATE.directory)
}),
response: {
200: z.object({
@ -73,15 +74,15 @@ export const registerSecretFolderRouter = async (server: FastifyZodProvider) =>
],
params: z.object({
// old way this was name
folderId: z.string()
folderId: z.string().describe(FOLDERS.UPDATE.folderId)
}),
body: z.object({
workspaceId: z.string().trim(),
environment: z.string().trim(),
name: z.string().trim(),
path: z.string().trim().default("/").transform(removeTrailingSlash),
workspaceId: z.string().trim().describe(FOLDERS.UPDATE.workspaceId),
environment: z.string().trim().describe(FOLDERS.UPDATE.environment),
name: z.string().trim().describe(FOLDERS.UPDATE.name),
path: z.string().trim().default("/").transform(removeTrailingSlash).describe(FOLDERS.UPDATE.path),
// backward compatiability with cli
directory: z.string().trim().default("/").transform(removeTrailingSlash)
directory: z.string().trim().default("/").transform(removeTrailingSlash).describe(FOLDERS.UPDATE.directory)
}),
response: {
200: z.object({
@ -119,6 +120,7 @@ export const registerSecretFolderRouter = async (server: FastifyZodProvider) =>
}
});
// TODO(daniel): Expose this route in api reference and write docs for it.
server.route({
url: "/:folderIdOrName",
method: "DELETE",
@ -131,14 +133,14 @@ export const registerSecretFolderRouter = async (server: FastifyZodProvider) =>
}
],
params: z.object({
folderIdOrName: z.string()
folderIdOrName: z.string().describe(FOLDERS.DELETE.folderIdOrName)
}),
body: z.object({
workspaceId: z.string().trim(),
environment: z.string().trim(),
path: z.string().trim().default("/").transform(removeTrailingSlash),
workspaceId: z.string().trim().describe(FOLDERS.DELETE.workspaceId),
environment: z.string().trim().describe(FOLDERS.DELETE.environment),
path: z.string().trim().default("/").transform(removeTrailingSlash).describe(FOLDERS.DELETE.path),
// keep this here as cli need directory
directory: z.string().trim().default("/").transform(removeTrailingSlash)
directory: z.string().trim().default("/").transform(removeTrailingSlash).describe(FOLDERS.DELETE.directory)
}),
response: {
200: z.object({
@ -187,11 +189,11 @@ export const registerSecretFolderRouter = async (server: FastifyZodProvider) =>
}
],
querystring: z.object({
workspaceId: z.string().trim(),
environment: z.string().trim(),
path: z.string().trim().default("/").transform(removeTrailingSlash),
workspaceId: z.string().trim().describe(FOLDERS.LIST.workspaceId),
environment: z.string().trim().describe(FOLDERS.LIST.environment),
path: z.string().trim().default("/").transform(removeTrailingSlash).describe(FOLDERS.LIST.path),
// backward compatiability with cli
directory: z.string().trim().default("/").transform(removeTrailingSlash)
directory: z.string().trim().default("/").transform(removeTrailingSlash).describe(FOLDERS.LIST.directory)
}),
response: {
200: z.object({

View File

@ -2,6 +2,7 @@ import { z } from "zod";
import { SecretImportsSchema, SecretsSchema } from "@app/db/schemas";
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
import { SECRET_IMPORTS } from "@app/lib/api-docs";
import { removeTrailingSlash } from "@app/lib/fn";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type";
@ -19,12 +20,12 @@ export const registerSecretImportRouter = async (server: FastifyZodProvider) =>
}
],
body: z.object({
workspaceId: z.string().trim(),
environment: z.string().trim(),
path: z.string().trim().default("/").transform(removeTrailingSlash),
workspaceId: z.string().trim().describe(SECRET_IMPORTS.CREATE.workspaceId),
environment: z.string().trim().describe(SECRET_IMPORTS.CREATE.environment),
path: z.string().trim().default("/").transform(removeTrailingSlash).describe(SECRET_IMPORTS.CREATE.path),
import: z.object({
environment: z.string().trim(),
path: z.string().trim().transform(removeTrailingSlash)
environment: z.string().trim().describe(SECRET_IMPORTS.CREATE.import.environment),
path: z.string().trim().transform(removeTrailingSlash).describe(SECRET_IMPORTS.CREATE.import.path)
})
}),
response: {
@ -80,20 +81,21 @@ export const registerSecretImportRouter = async (server: FastifyZodProvider) =>
}
],
params: z.object({
secretImportId: z.string().trim()
secretImportId: z.string().trim().describe(SECRET_IMPORTS.UPDATE.secretImportId)
}),
body: z.object({
workspaceId: z.string().trim(),
environment: z.string().trim(),
path: z.string().trim().default("/").transform(removeTrailingSlash),
workspaceId: z.string().trim().describe(SECRET_IMPORTS.UPDATE.workspaceId),
environment: z.string().trim().describe(SECRET_IMPORTS.UPDATE.environment),
path: z.string().trim().default("/").transform(removeTrailingSlash).describe(SECRET_IMPORTS.UPDATE.path),
import: z.object({
environment: z.string().trim().optional(),
environment: z.string().trim().optional().describe(SECRET_IMPORTS.UPDATE.import.environment),
path: z
.string()
.trim()
.optional()
.transform((val) => (val ? removeTrailingSlash(val) : val)),
position: z.number().optional()
.transform((val) => (val ? removeTrailingSlash(val) : val))
.describe(SECRET_IMPORTS.UPDATE.import.path),
position: z.number().optional().describe(SECRET_IMPORTS.UPDATE.import.position)
})
}),
response: {
@ -150,12 +152,12 @@ export const registerSecretImportRouter = async (server: FastifyZodProvider) =>
}
],
params: z.object({
secretImportId: z.string().trim()
secretImportId: z.string().trim().describe(SECRET_IMPORTS.DELETE.secretImportId)
}),
body: z.object({
workspaceId: z.string().trim(),
environment: z.string().trim(),
path: z.string().trim().default("/").transform(removeTrailingSlash)
workspaceId: z.string().trim().describe(SECRET_IMPORTS.DELETE.workspaceId),
environment: z.string().trim().describe(SECRET_IMPORTS.DELETE.environment),
path: z.string().trim().default("/").transform(removeTrailingSlash).describe(SECRET_IMPORTS.DELETE.path)
}),
response: {
200: z.object({
@ -210,9 +212,9 @@ export const registerSecretImportRouter = async (server: FastifyZodProvider) =>
}
],
querystring: z.object({
workspaceId: z.string().trim(),
environment: z.string().trim(),
path: z.string().trim().default("/").transform(removeTrailingSlash)
workspaceId: z.string().trim().describe(SECRET_IMPORTS.LIST.workspaceId),
environment: z.string().trim().describe(SECRET_IMPORTS.LIST.environment),
path: z.string().trim().default("/").transform(removeTrailingSlash).describe(SECRET_IMPORTS.LIST.path)
}),
response: {
200: z.object({

View File

@ -1,6 +1,7 @@
import { z } from "zod";
import { IdentitiesSchema, IdentityOrgMembershipsSchema, OrgRolesSchema } from "@app/db/schemas";
import { ORGANIZATIONS } from "@app/lib/api-docs";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type";
@ -18,7 +19,7 @@ export const registerIdentityOrgRouter = async (server: FastifyZodProvider) => {
}
],
params: z.object({
orgId: z.string().trim()
orgId: z.string().trim().describe(ORGANIZATIONS.LIST_IDENTITY_MEMBERSHIPS.orgId)
}),
response: {
200: z.object({

View File

@ -7,6 +7,7 @@ import {
ProjectMembershipRole,
ProjectUserMembershipRolesSchema
} from "@app/db/schemas";
import { PROJECTS } from "@app/lib/api-docs";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type";
import { ProjectUserMembershipTemporaryMode } from "@app/services/project-membership/project-membership-types";
@ -55,8 +56,8 @@ export const registerIdentityProjectRouter = async (server: FastifyZodProvider)
}
],
params: z.object({
projectId: z.string().trim(),
identityId: z.string().trim()
projectId: z.string().trim().describe(PROJECTS.UPDATE_IDENTITY_MEMBERSHIP.projectId),
identityId: z.string().trim().describe(PROJECTS.UPDATE_IDENTITY_MEMBERSHIP.identityId)
}),
body: z.object({
roles: z
@ -76,6 +77,7 @@ export const registerIdentityProjectRouter = async (server: FastifyZodProvider)
])
)
.min(1)
.describe(PROJECTS.UPDATE_IDENTITY_MEMBERSHIP.roles)
}),
response: {
200: z.object({
@ -108,8 +110,8 @@ export const registerIdentityProjectRouter = async (server: FastifyZodProvider)
}
],
params: z.object({
projectId: z.string().trim(),
identityId: z.string().trim()
projectId: z.string().trim().describe(PROJECTS.DELETE_IDENTITY_MEMBERSHIP.projectId),
identityId: z.string().trim().describe(PROJECTS.DELETE_IDENTITY_MEMBERSHIP.identityId)
}),
response: {
200: z.object({
@ -141,7 +143,7 @@ export const registerIdentityProjectRouter = async (server: FastifyZodProvider)
}
],
params: z.object({
projectId: z.string().trim()
projectId: z.string().trim().describe(PROJECTS.LIST_IDENTITY_MEMBERSHIPS.projectId)
}),
response: {
200: z.object({

View File

@ -1,6 +1,7 @@
import { z } from "zod";
import { OrganizationsSchema, OrgMembershipsSchema, UserEncryptionKeysSchema, UsersSchema } from "@app/db/schemas";
import { ORGANIZATIONS } from "@app/lib/api-docs";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { ActorType, AuthMode } from "@app/services/auth/auth-type";
@ -17,7 +18,7 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => {
}
],
params: z.object({
organizationId: z.string().trim()
organizationId: z.string().trim().describe(ORGANIZATIONS.LIST_USER_MEMBERSHIPS.organizationId)
}),
response: {
200: z.object({
@ -62,7 +63,7 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => {
}
],
params: z.object({
organizationId: z.string().trim()
organizationId: z.string().trim().describe(ORGANIZATIONS.GET_PROJECTS.organizationId)
}),
response: {
200: z.object({
@ -106,9 +107,12 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => {
apiKeyAuth: []
}
],
params: z.object({ organizationId: z.string().trim(), membershipId: z.string().trim() }),
params: z.object({
organizationId: z.string().trim().describe(ORGANIZATIONS.UPDATE_USER_MEMBERSHIP.organizationId),
membershipId: z.string().trim().describe(ORGANIZATIONS.UPDATE_USER_MEMBERSHIP.membershipId)
}),
body: z.object({
role: z.string().trim()
role: z.string().trim().describe(ORGANIZATIONS.UPDATE_USER_MEMBERSHIP.role)
}),
response: {
200: z.object({
@ -142,7 +146,10 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => {
apiKeyAuth: []
}
],
params: z.object({ organizationId: z.string().trim(), membershipId: z.string().trim() }),
params: z.object({
organizationId: z.string().trim().describe(ORGANIZATIONS.DELETE_USER_MEMBERSHIP.organizationId),
membershipId: z.string().trim().describe(ORGANIZATIONS.DELETE_USER_MEMBERSHIP.membershipId)
}),
response: {
200: z.object({
membership: OrgMembershipsSchema

View File

@ -2,6 +2,7 @@ import { z } from "zod";
import { ProjectMembershipsSchema } from "@app/db/schemas";
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
import { PROJECTS } from "@app/lib/api-docs";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type";
@ -11,11 +12,11 @@ export const registerProjectMembershipRouter = async (server: FastifyZodProvider
url: "/:projectId/memberships",
schema: {
params: z.object({
projectId: z.string().describe("The ID of the project.")
projectId: z.string().describe(PROJECTS.INVITE_MEMBER.projectId)
}),
body: z.object({
emails: z.string().email().array().default([]).describe("Emails of the users to add to the project."),
usernames: z.string().array().default([]).describe("Usernames of the users to add to the project.")
emails: z.string().email().array().default([]).describe(PROJECTS.INVITE_MEMBER.emails),
usernames: z.string().array().default([]).describe(PROJECTS.INVITE_MEMBER.usernames)
}),
response: {
200: z.object({
@ -55,12 +56,12 @@ export const registerProjectMembershipRouter = async (server: FastifyZodProvider
url: "/:projectId/memberships",
schema: {
params: z.object({
projectId: z.string().describe("The ID of the project.")
projectId: z.string().describe(PROJECTS.REMOVE_MEMBER.projectId)
}),
body: z.object({
emails: z.string().email().array().default([]).describe("Emails of the users to remove from the project."),
usernames: z.string().array().default([]).describe("Usernames of the users to remove from the project.")
emails: z.string().email().array().default([]).describe(PROJECTS.REMOVE_MEMBER.emails),
usernames: z.string().array().default([]).describe(PROJECTS.REMOVE_MEMBER.usernames)
}),
response: {
200: z.object({

View File

@ -3,6 +3,7 @@ import { z } from "zod";
import { ProjectKeysSchema, ProjectsSchema } from "@app/db/schemas";
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
import { PROJECTS } from "@app/lib/api-docs";
import { authRateLimit } from "@app/server/config/rateLimiter";
import { getTelemetryDistinctId } from "@app/server/lib/telemetry";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
@ -29,7 +30,7 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
}
],
params: z.object({
workspaceId: z.string().trim()
workspaceId: z.string().trim().describe(PROJECTS.GET_KEY.workspaceId)
}),
response: {
200: ProjectKeysSchema.merge(
@ -127,7 +128,7 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
},
schema: {
body: z.object({
projectName: z.string().trim(),
projectName: z.string().trim().describe(PROJECTS.CREATE.projectName),
slug: z
.string()
.min(5)
@ -135,8 +136,9 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
.refine((v) => slugify(v) === v, {
message: "Slug must be a valid slug"
})
.optional(),
organizationId: z.string().trim()
.optional()
.describe(PROJECTS.CREATE.slug),
organizationId: z.string().trim().describe(PROJECTS.CREATE.organizationId)
}),
response: {
200: z.object({

View File

@ -10,6 +10,7 @@ import {
} from "@app/db/schemas";
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
import { CommitType } from "@app/ee/services/secret-approval-request/secret-approval-request-types";
import { RAW_SECRETS } from "@app/lib/api-docs";
import { BadRequestError } from "@app/lib/errors";
import { removeTrailingSlash } from "@app/lib/fn";
import { getTelemetryDistinctId } from "@app/server/lib/telemetry";
@ -33,13 +34,14 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
}
],
querystring: z.object({
workspaceId: z.string().trim().optional(),
environment: z.string().trim().optional(),
secretPath: z.string().trim().default("/").transform(removeTrailingSlash),
workspaceId: z.string().trim().optional().describe(RAW_SECRETS.LIST.workspaceId),
environment: z.string().trim().optional().describe(RAW_SECRETS.LIST.environment),
secretPath: z.string().trim().default("/").transform(removeTrailingSlash).describe(RAW_SECRETS.LIST.secretPath),
include_imports: z
.enum(["true", "false"])
.default("false")
.transform((value) => value === "true")
.describe(RAW_SECRETS.LIST.includeImports)
}),
response: {
200: z.object({
@ -123,18 +125,19 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
}
],
params: z.object({
secretName: z.string().trim()
secretName: z.string().trim().describe(RAW_SECRETS.GET.secretName)
}),
querystring: z.object({
workspaceId: z.string().trim().optional(),
environment: z.string().trim().optional(),
secretPath: z.string().trim().default("/").transform(removeTrailingSlash),
version: z.coerce.number().optional(),
type: z.nativeEnum(SecretType).default(SecretType.Shared),
workspaceId: z.string().trim().optional().describe(RAW_SECRETS.GET.workspaceId),
environment: z.string().trim().optional().describe(RAW_SECRETS.GET.environment),
secretPath: z.string().trim().default("/").transform(removeTrailingSlash).describe(RAW_SECRETS.GET.secretPath),
version: z.coerce.number().optional().describe(RAW_SECRETS.GET.version),
type: z.nativeEnum(SecretType).default(SecretType.Shared).describe(RAW_SECRETS.GET.type),
include_imports: z
.enum(["true", "false"])
.default("false")
.transform((value) => value === "true")
.describe(RAW_SECRETS.GET.includeImports)
}),
response: {
200: z.object({
@ -213,16 +216,24 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
}
],
params: z.object({
secretName: z.string().trim()
secretName: z.string().trim().describe(RAW_SECRETS.CREATE.secretName)
}),
body: z.object({
workspaceId: z.string().trim(),
environment: z.string().trim(),
secretPath: z.string().trim().default("/").transform(removeTrailingSlash),
secretValue: z.string().transform((val) => (val.at(-1) === "\n" ? `${val.trim()}\n` : val.trim())),
secretComment: z.string().trim().optional().default(""),
skipMultilineEncoding: z.boolean().optional(),
type: z.nativeEnum(SecretType).default(SecretType.Shared)
workspaceId: z.string().trim().describe(RAW_SECRETS.CREATE.workspaceId),
environment: z.string().trim().describe(RAW_SECRETS.CREATE.environment),
secretPath: z
.string()
.trim()
.default("/")
.transform(removeTrailingSlash)
.describe(RAW_SECRETS.CREATE.secretPath),
secretValue: z
.string()
.transform((val) => (val.at(-1) === "\n" ? `${val.trim()}\n` : val.trim()))
.describe(RAW_SECRETS.CREATE.secretValue),
secretComment: z.string().trim().optional().default("").describe(RAW_SECRETS.CREATE.secretComment),
skipMultilineEncoding: z.boolean().optional().describe(RAW_SECRETS.CREATE.skipMultilineEncoding),
type: z.nativeEnum(SecretType).default(SecretType.Shared).describe(RAW_SECRETS.CREATE.type)
}),
response: {
200: z.object({
@ -290,15 +301,23 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
}
],
params: z.object({
secretName: z.string().trim()
secretName: z.string().trim().describe(RAW_SECRETS.UPDATE.secretName)
}),
body: z.object({
workspaceId: z.string().trim(),
environment: z.string().trim(),
secretValue: z.string().transform((val) => (val.at(-1) === "\n" ? `${val.trim()}\n` : val.trim())),
secretPath: z.string().trim().default("/").transform(removeTrailingSlash),
skipMultilineEncoding: z.boolean().optional(),
type: z.nativeEnum(SecretType).default(SecretType.Shared)
workspaceId: z.string().trim().describe(RAW_SECRETS.UPDATE.workspaceId),
environment: z.string().trim().describe(RAW_SECRETS.UPDATE.environment),
secretValue: z
.string()
.transform((val) => (val.at(-1) === "\n" ? `${val.trim()}\n` : val.trim()))
.describe(RAW_SECRETS.UPDATE.secretValue),
secretPath: z
.string()
.trim()
.default("/")
.transform(removeTrailingSlash)
.describe(RAW_SECRETS.UPDATE.secretPath),
skipMultilineEncoding: z.boolean().optional().describe(RAW_SECRETS.UPDATE.skipMultilineEncoding),
type: z.nativeEnum(SecretType).default(SecretType.Shared).describe(RAW_SECRETS.UPDATE.type)
}),
response: {
200: z.object({
@ -364,13 +383,18 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
}
],
params: z.object({
secretName: z.string().trim()
secretName: z.string().trim().describe(RAW_SECRETS.DELETE.secretName)
}),
body: z.object({
workspaceId: z.string().trim(),
environment: z.string().trim(),
secretPath: z.string().trim().default("/").transform(removeTrailingSlash),
type: z.nativeEnum(SecretType).default(SecretType.Shared)
workspaceId: z.string().trim().describe(RAW_SECRETS.DELETE.workspaceId),
environment: z.string().trim().describe(RAW_SECRETS.DELETE.environment),
secretPath: z
.string()
.trim()
.default("/")
.transform(removeTrailingSlash)
.describe(RAW_SECRETS.DELETE.secretPath),
type: z.nativeEnum(SecretType).default(SecretType.Shared).describe(RAW_SECRETS.DELETE.type)
}),
response: {
200: z.object({

View File

@ -260,20 +260,44 @@ const getAppsGithub = async ({ accessToken }: { accessToken: string }) => {
* Return list of services for Render integration
*/
const getAppsRender = async ({ accessToken }: { accessToken: string }) => {
const res = (
await request.get<{ service: { name: string; id: string } }[]>(`${IntegrationUrls.RENDER_API_URL}/v1/services`, {
headers: {
Authorization: `Bearer ${accessToken}`,
Accept: "application/json",
"Accept-Encoding": "application/json"
}
})
).data;
const apps: Array<{ name: string; appId: string }> = [];
let hasMorePages = true;
const perPage = 100;
let cursor;
const apps = res.map((a) => ({
name: a.service.name,
appId: a.service.id
}));
interface RenderService {
cursor: string;
service: { name: string; id: string };
}
while (hasMorePages) {
const res: RenderService[] = (
await request.get<RenderService[]>(`${IntegrationUrls.RENDER_API_URL}/v1/services`, {
params: new URLSearchParams({
...(cursor ? { cursor: String(cursor) } : {}),
limit: String(perPage)
}),
headers: {
Authorization: `Bearer ${accessToken}`,
Accept: "application/json",
"Accept-Encoding": "application/json"
}
})
).data;
res.forEach((a) => {
apps.push({
name: a.service.name,
appId: a.service.id
});
});
if (res.length < perPage) {
hasMorePages = false;
} else {
cursor = res[res.length - 1].cursor;
}
}
return apps;
};

View File

@ -459,7 +459,7 @@ const syncSecretsAWSParameterStore = async ({
const params = {
Path: integration.path as string,
Recursive: true,
Recursive: false,
WithDecryption: true
};

View File

@ -23,7 +23,8 @@ export default defineConfig({
loader: {
".handlebars": "copy",
".md": "copy",
".txt": "copy"
".txt": "copy",
".pem": "copy"
},
external: ["../../../frontend/node_modules/next/dist/server/next-server.js"],
outDir: "dist",

View File

@ -1,4 +1,4 @@
---
title: "Delete"
openapi: "DELETE /api/v1/folders/{folderId}"
openapi: "DELETE /api/v1/folders/{folderIdOrName}"
---

View File

@ -39,32 +39,32 @@
"name": "Start for Free",
"url": "https://app.infisical.com/signup"
},
"anchors": [
"tabs": [
{
"name": "Internals",
"icon": "sitemap",
"url": "internals"
},
{
"name": "SDKs",
"icon": "puzzle-piece",
"url": "sdks"
"name": "Changelog",
"url": "changelog"
},
{
"name": "API Reference",
"icon": "cloud",
"url": "api-reference"
},
{
"name": "Changelog",
"icon": "timer",
"url": "changelog"
"name": "SDKs",
"url": "sdks"
},
{
"name": "Contributing",
"url": "contributing"
}
],
"anchors": [
{
"name": "Contributing",
"icon": "code",
"url": "contributing"
},
{
"name": "Blog",
"icon": "newspaper",
@ -79,6 +79,11 @@
"name": "GitHub",
"icon": "github",
"url": "https://github.com/Infisical/infisical"
},
{
"name": "Internals",
"icon": "sitemap",
"url": "internals"
}
],
"navigation": [
@ -191,6 +196,7 @@
"self-hosting/guides/mongo-to-postgres"
]
},
"self-hosting/ee",
"self-hosting/faq"
]
},
@ -356,7 +362,18 @@
},
{
"group": "Overview",
"pages": ["sdks/overview"]
"pages": [
"sdks/overview"
]
},
{
"group": "SDK's",
"pages": [
"sdks/languages/node",
"sdks/languages/python",
"sdks/languages/java",
"sdks/languages/csharp"
]
},
{
"group": "Overview",

View File

@ -1,6 +1,7 @@
---
title: "Infisical .NET SDK"
icon: "C#"
sidebarTitle: ".NET"
icon: "bars"
---
If you're working with C#, the official [Infisical C# SDK](https://github.com/Infisical/sdk/tree/main/languages/csharp) package is the easiest way to fetch and work with secrets for your application.

View File

@ -1,5 +1,6 @@
---
title: "Infisical Java SDK"
sidebarTitle: "Java"
icon: "java"
---

View File

@ -1,5 +1,6 @@
---
title: "Infisical Node.js SDK"
sidebarTitle: "Node.js"
icon: "node"
---

View File

@ -1,5 +1,6 @@
---
title: "Infisical Python SDK"
sidebarTitle: "Python"
icon: "python"
---

28
docs/self-hosting/ee.mdx Normal file
View File

@ -0,0 +1,28 @@
---
title: "Using Infisical EE"
description: "How to activate Infisical Enterprise Edition (EE) features"
---
While most features in Infisical are free to use, others are paid and require purchasing an enterprise license to use them.
This guide walks through how you can use these paid features in Infisical.
<Steps>
<Step title="Purchase a license">
Start by either signing up for a free demo [here](https://infisical.com/schedule-demo) or contacting team@infisical.com to purchase a license.
Once purchased, you will be issued a license key.
</Step>
<Step title="Activate the license">
Depending on whether or not the environment where Infisical is deployed has internet access, you may be issued a regular license or an offline license.
- If using a regular license, you should set the value of the environment variable `LICENSE_KEY` in Infisical to the issued license key.
- If using an offline license, you should set the value of the environment variable `LICENSE_KEY_OFFLINE` in Infisical to the issued license key.
Once your instance starts up, the license key will be validated and youll be able to use the paid features.
<Note>
Once the license expires, Infisical will continue to run, but EE features will be disabled until the license is renewed or a new one is purchased.
</Note>
</Step>
</Steps>

View File

@ -302,7 +302,7 @@
"auto-generated": "This is your project's auto-generated unique identifier. It can't be changed.",
"docs": "Infisical Docs",
"auto-capitalization": "Auto Capitalization",
"auto-capitalization-description": "According to standards, Infisical will automatically capitalize your keys. If you want to disable this feature, you can do so here."
"auto-capitalization-description": "Infisical will automatically capitalize your keys. If you want to disable this feature, you can do so here."
}
},
"signup": {

View File

@ -290,7 +290,7 @@
"auto-generated": "Este es el ID único y autogenerado de proyecto. No se puede modificar.",
"docs": "Documentación de Infisical",
"auto-capitalization": "Mayúsculas automáticas",
"auto-capitalization-description": "De acuerdo con los estándares, Infisical pondrá en mayúsculas tus claves. Si quieres desactivar esta funcionalidad, lo puedes hacer aquí."
"auto-capitalization-description": "Infisical pondrá en mayúsculas tus claves. Si quieres desactivar esta funcionalidad, lo puedes hacer aquí."
}
},
"signup": {

View File

@ -267,7 +267,7 @@
"auto-generated": "Este é o identificador exclusivo - gerado automaticamente - do seu projeto. Não pode ser alterado.",
"docs": "Documentação do Infisical",
"auto-capitalization": "Converter em caixa alta automaticamente",
"auto-capitalization-description": "Por padrão, Infisical converte automaticamente as chaves em caixa alta. Se você quiser desativar essa funcionalidade, pode fazê-lo aqui."
"auto-capitalization-description": "Infisical converte automaticamente as chaves em caixa alta. Se você quiser desativar essa funcionalidade, pode fazê-lo aqui."
}
},
"signup": {

View File

@ -23,9 +23,6 @@ export const MembersPage = withPermission(
<Tab value={TabSections.Identities}>
<div className="flex items-center">
<p>Machine Identities</p>
<div className="ml-2 inline-block cursor-default rounded-md bg-yellow/20 px-1.5 pb-[0.03rem] pt-[0.04rem] text-sm text-yellow opacity-80 hover:opacity-100">
New
</div>
</div>
</Tab>
<Tab value={TabSections.Roles}>Organization Roles</Tab>

View File

@ -1,10 +1,13 @@
import Link from "next/link";
import { faArrowUpRightFromSquare, faPlus } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
// import Link from "next/link";
// import { faArrowUpRightFromSquare, faPlus } from "@fortawesome/free-solid-svg-icons";
// import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider";
import { OrgPermissionCan } from "@app/components/permissions";
import { Button, DeleteActionModal } from "@app/components/v2";
// import { OrgPermissionCan } from "@app/components/permissions";
import {
// Button,
DeleteActionModal
} from "@app/components/v2";
import { OrgPermissionActions, OrgPermissionSubjects, useOrganization } from "@app/context";
import { withPermission } from "@app/hoc";
import { useDeleteIdentity } from "@app/hooks/api";
@ -58,9 +61,10 @@ export const IdentitySection = withPermission(
return (
<div className="mb-6 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
<div className="mb-4 flex justify-between">
<p className="text-xl font-semibold text-mineshaft-100">Identities</p>
<div className="flex w-full justify-end pr-4">
<div className="py-4">
<p className="mb-2 text-md text-mineshaft-100">Machine Identities</p>
<p className="text-sm text-mineshaft-300">Manage which apps/services have access to this organization</p>
{/* <div className="flex w-full justify-end pr-4">
<Link href="https://infisical.com/docs/documentation/platform/identities/overview">
<a
target="_blank"
@ -74,8 +78,8 @@ export const IdentitySection = withPermission(
/>
</a>
</Link>
</div>
<OrgPermissionCan I={OrgPermissionActions.Create} a={OrgPermissionSubjects.Identity}>
</div> */}
{/* <OrgPermissionCan I={OrgPermissionActions.Create} a={OrgPermissionSubjects.Identity}>
{(isAllowed) => (
<Button
colorSchema="primary"
@ -87,7 +91,7 @@ export const IdentitySection = withPermission(
Create identity
</Button>
)}
</OrgPermissionCan>
</OrgPermissionCan> */}
</div>
<IdentityTable handlePopUpOpen={handlePopUpOpen} />
<IdentityModal

View File

@ -1,11 +1,14 @@
import { faKey, faLock, faPencil, faServer, faXmark } from "@fortawesome/free-solid-svg-icons";
import { useState } from "react";
import { faKey, faLock, faMagnifyingGlass, faPencil, faPlus, faServer, faXmark } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider";
import { OrgPermissionCan } from "@app/components/permissions";
import {
Button,
EmptyState,
IconButton,
Input,
Select,
SelectItem,
Table,
@ -49,9 +52,11 @@ export const IdentityTable = ({ handlePopUpOpen }: Props) => {
const orgId = currentOrg?.id || "";
const { mutateAsync: updateMutateAsync } = useUpdateIdentity();
const { data, isLoading } = useGetIdentityMembershipOrgs(orgId);
const { data: identities, isLoading } = useGetIdentityMembershipOrgs(orgId);
const { data: roles } = useGetOrgRoles(orgId);
const [searchIdentity, setSearchIdentity] = useState("");
const handleChangeRole = async ({ identityId, role }: { identityId: string; role: string }) => {
try {
@ -76,92 +81,140 @@ export const IdentityTable = ({ handlePopUpOpen }: Props) => {
});
}
};
const filteredIdentities = identities ? identities.filter(({ identity: { name } }) => name.toLocaleLowerCase().includes(searchIdentity.toLocaleLowerCase())) : [];
return (
<TableContainer>
<Table>
<THead>
<Tr>
<Th>Name</Th>
<Th>ID</Th>
<Th>Role</Th>
<Th>Auth Method</Th>
<Th className="w-5" />
</Tr>
</THead>
<TBody>
{isLoading && <TableSkeleton columns={4} innerKey="org-identities" />}
{!isLoading &&
data &&
data.length > 0 &&
data.map(({ identity: { id, name, authMethod }, role, customRole }) => {
return (
<Tr className="h-10" key={`identity-${id}`}>
<Td>{name}</Td>
<Td>{id}</Td>
<Td>
<OrgPermissionCan
I={OrgPermissionActions.Edit}
a={OrgPermissionSubjects.Identity}
>
{(isAllowed) => {
return (
<Select
value={role === "custom" ? (customRole?.slug as string) : role}
isDisabled={!isAllowed}
className="w-40 bg-mineshaft-600"
dropdownContainerClassName="border border-mineshaft-600 bg-mineshaft-800"
onValueChange={(selectedRole) =>
handleChangeRole({
identityId: id,
role: selectedRole
})
}
>
{(roles || []).map(({ slug, name: roleName }) => (
<SelectItem value={slug} key={`owner-option-${slug}`}>
{roleName}
</SelectItem>
))}
</Select>
);
}}
</OrgPermissionCan>
</Td>
<Td>{authMethod ? identityAuthToNameMap[authMethod] : "Not configured"}</Td>
<Td>
<div className="flex items-center justify-end">
{authMethod === IdentityAuthMethod.UNIVERSAL_AUTH && (
<Tooltip content="Manage client ID/secrets">
<IconButton
onClick={async () => {
handlePopUpOpen("universalAuthClientSecret", {
identityId: id,
name
});
}}
size="lg"
colorSchema="primary"
variant="plain"
ariaLabel="update"
// isDisabled={!isAllowed}
>
<FontAwesomeIcon icon={faKey} />
</IconButton>
</Tooltip>
)}
<div>
<div className="flex">
<Input
value={searchIdentity}
onChange={(e) => setSearchIdentity(e.target.value)}
leftIcon={<FontAwesomeIcon icon={faMagnifyingGlass} />}
placeholder="Search identities..."
/>
<OrgPermissionCan I={OrgPermissionActions.Create} a={OrgPermissionSubjects.Identity}>
{(isAllowed) => (
<Button
colorSchema="secondary"
type="submit"
leftIcon={<FontAwesomeIcon icon={faPlus} />}
onClick={() => handlePopUpOpen("identity")}
isDisabled={!isAllowed}
className="ml-4"
>
Create identity
</Button>
)}
</OrgPermissionCan>
</div>
<TableContainer className="mt-4">
<Table>
<THead>
<Tr>
<Th>Name</Th>
<Th>ID</Th>
<Th>Role</Th>
<Th>Auth Method</Th>
<Th className="w-5" />
</Tr>
</THead>
<TBody>
{isLoading && <TableSkeleton columns={4} innerKey="org-identities" />}
{!isLoading && filteredIdentities?.map(({ identity: { id, name, authMethod }, role, customRole }) => {
return (
<Tr className="h-10" key={`identity-${id}`}>
<Td>{name}</Td>
<Td>{id}</Td>
<Td>
<OrgPermissionCan
I={OrgPermissionActions.Edit}
a={OrgPermissionSubjects.Identity}
>
{(isAllowed) => (
<Tooltip content="Manage auth method">
{(isAllowed) => {
return (
<Select
value={role === "custom" ? (customRole?.slug as string) : role}
isDisabled={!isAllowed}
className="w-40 bg-mineshaft-600"
dropdownContainerClassName="border border-mineshaft-600 bg-mineshaft-800"
onValueChange={(selectedRole) =>
handleChangeRole({
identityId: id,
role: selectedRole
})
}
>
{(roles || []).map(({ slug, name: roleName }) => (
<SelectItem value={slug} key={`owner-option-${slug}`}>
{roleName}
</SelectItem>
))}
</Select>
);
}}
</OrgPermissionCan>
</Td>
<Td>{authMethod ? identityAuthToNameMap[authMethod] : "Not configured"}</Td>
<Td>
<div className="flex items-center justify-end">
{authMethod === IdentityAuthMethod.UNIVERSAL_AUTH && (
<Tooltip content="Manage client ID/secrets">
<IconButton
onClick={async () => {
handlePopUpOpen("identityAuthMethod", {
handlePopUpOpen("universalAuthClientSecret", {
identityId: id,
name
});
}}
size="lg"
colorSchema="primary"
variant="plain"
ariaLabel="update"
// isDisabled={!isAllowed}
>
<FontAwesomeIcon icon={faKey} />
</IconButton>
</Tooltip>
)}
<OrgPermissionCan
I={OrgPermissionActions.Edit}
a={OrgPermissionSubjects.Identity}
>
{(isAllowed) => (
<Tooltip content="Manage auth method">
<IconButton
onClick={async () => {
handlePopUpOpen("identityAuthMethod", {
identityId: id,
name,
authMethod
});
}}
size="lg"
colorSchema="primary"
variant="plain"
ariaLabel="update"
className="ml-4"
isDisabled={!isAllowed}
>
<FontAwesomeIcon icon={faLock} />
</IconButton>
</Tooltip>
)}
</OrgPermissionCan>
<OrgPermissionCan
I={OrgPermissionActions.Edit}
a={OrgPermissionSubjects.Identity}
>
{(isAllowed) => (
<IconButton
onClick={async () => {
handlePopUpOpen("identity", {
identityId: id,
name,
authMethod
role,
customRole
});
}}
size="lg"
@ -171,76 +224,47 @@ export const IdentityTable = ({ handlePopUpOpen }: Props) => {
className="ml-4"
isDisabled={!isAllowed}
>
<FontAwesomeIcon icon={faLock} />
<FontAwesomeIcon icon={faPencil} />
</IconButton>
</Tooltip>
)}
</OrgPermissionCan>
<OrgPermissionCan
I={OrgPermissionActions.Edit}
a={OrgPermissionSubjects.Identity}
>
{(isAllowed) => (
<IconButton
onClick={async () => {
handlePopUpOpen("identity", {
identityId: id,
name,
role,
customRole
});
}}
size="lg"
colorSchema="primary"
variant="plain"
ariaLabel="update"
className="ml-4"
isDisabled={!isAllowed}
>
<FontAwesomeIcon icon={faPencil} />
</IconButton>
)}
</OrgPermissionCan>
<OrgPermissionCan
I={OrgPermissionActions.Delete}
a={OrgPermissionSubjects.Identity}
>
{(isAllowed) => (
<IconButton
onClick={() => {
handlePopUpOpen("deleteIdentity", {
identityId: id,
name
});
}}
size="lg"
colorSchema="danger"
variant="plain"
ariaLabel="update"
className="ml-4"
isDisabled={!isAllowed}
>
<FontAwesomeIcon icon={faXmark} />
</IconButton>
)}
</OrgPermissionCan>
</div>
</Td>
</Tr>
);
})}
{!isLoading && data && data?.length === 0 && (
<Tr>
<Td colSpan={4}>
<EmptyState
title="No identities have been created in this organization"
icon={faServer}
/>
</Td>
</Tr>
)}
</TBody>
</Table>
</TableContainer>
)}
</OrgPermissionCan>
<OrgPermissionCan
I={OrgPermissionActions.Delete}
a={OrgPermissionSubjects.Identity}
>
{(isAllowed) => (
<IconButton
onClick={() => {
handlePopUpOpen("deleteIdentity", {
identityId: id,
name
});
}}
size="lg"
colorSchema="danger"
variant="plain"
ariaLabel="update"
className="ml-4"
isDisabled={!isAllowed}
>
<FontAwesomeIcon icon={faXmark} />
</IconButton>
)}
</OrgPermissionCan>
</div>
</Td>
</Tr>
);
})}
</TBody>
</Table>
{!isLoading && filteredIdentities?.length === 0 && (
<EmptyState
title={searchIdentity === "" ? "No identities have been created in this organization" : "No matching identities found"}
icon={faServer}
/>
)}
</TableContainer>
</div>
);
};

View File

@ -1,21 +1,12 @@
import { useState } from "react";
import { faPlus } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider";
import { OrgPermissionCan } from "@app/components/permissions";
import {
Button,
DeleteActionModal,
EmailServiceSetupModal,
UpgradePlanModal
} from "@app/components/v2";
import {
OrgPermissionActions,
OrgPermissionSubjects,
useOrganization,
useSubscription
} from "@app/context";
import { useOrganization } from "@app/context";
import { useDeleteOrgMembership } from "@app/hooks/api";
import { usePopUp } from "@app/hooks/usePopUp";
@ -24,7 +15,6 @@ import { OrgMembersTable } from "./OrgMembersTable";
export const OrgMembersSection = () => {
const { createNotification } = useNotificationContext();
const { subscription } = useSubscription();
const { currentOrg } = useOrganization();
const orgId = currentOrg?.id ?? "";
@ -39,28 +29,6 @@ export const OrgMembersSection = () => {
const { mutateAsync: deleteMutateAsync } = useDeleteOrgMembership();
const isMoreUsersNotAllowed = subscription?.memberLimit
? subscription.membersUsed >= subscription.memberLimit
: false;
const handleAddMemberModal = () => {
if (currentOrg?.authEnforced) {
createNotification({
text: "You cannot manage users from Infisical when org-level auth is enforced for your organization",
type: "error"
});
return;
}
if (isMoreUsersNotAllowed) {
handlePopUpOpen("upgradePlan", {
description: "You can add more members if you upgrade your Infisical plan."
});
} else {
handlePopUpOpen("addMember");
}
};
const onRemoveMemberSubmit = async (orgMembershipId: string) => {
try {
await deleteMutateAsync({
@ -85,21 +53,9 @@ export const OrgMembersSection = () => {
return (
<div className="mb-6 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
<div className="mb-4 flex justify-between">
<p className="text-xl font-semibold text-mineshaft-100">Members</p>
<OrgPermissionCan I={OrgPermissionActions.Create} a={OrgPermissionSubjects.Member}>
{(isAllowed) => (
<Button
colorSchema="primary"
type="submit"
leftIcon={<FontAwesomeIcon icon={faPlus} />}
onClick={() => handleAddMemberModal()}
isDisabled={!isAllowed}
>
Add Member
</Button>
)}
</OrgPermissionCan>
<div className="py-4">
<h2 className="mb-2 text-md text-mineshaft-100">Members</h2>
<p className="text-sm text-mineshaft-300">Manage who has access to this organization</p>
</div>
<OrgMembersTable
handlePopUpOpen={handlePopUpOpen}

View File

@ -1,5 +1,5 @@
import { useCallback, useMemo, useState } from "react";
import { faMagnifyingGlass, faUsers, faXmark } from "@fortawesome/free-solid-svg-icons";
import { faMagnifyingGlass, faPlus,faUsers, faXmark } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider";
@ -38,7 +38,7 @@ import { UsePopUpState } from "@app/hooks/usePopUp";
type Props = {
handlePopUpOpen: (
popUpName: keyof UsePopUpState<["removeMember", "upgradePlan"]>,
popUpName: keyof UsePopUpState<["addMember", "removeMember", "upgradePlan"]>,
data?: {
orgMembershipId?: string;
username?: string;
@ -66,6 +66,28 @@ export const OrgMembersTable = ({ handlePopUpOpen, setCompleteInviteLink }: Prop
const { mutateAsync: addUserMutateAsync } = useAddUserToOrg();
const { mutateAsync: updateUserOrgRole } = useUpdateOrgUserRole();
const isMoreUsersNotAllowed = subscription?.memberLimit
? subscription.membersUsed >= subscription.memberLimit
: false;
const handleAddMemberModal = () => {
if (currentOrg?.authEnforced) {
createNotification({
text: "You cannot manage users from Infisical when org-level auth is enforced for your organization",
type: "error"
});
return;
}
if (isMoreUsersNotAllowed) {
handlePopUpOpen("upgradePlan", {
description: "You can add more members if you upgrade your Infisical plan."
});
} else {
handlePopUpOpen("addMember");
}
};
const onRoleChange = async (membershipId: string, role: string) => {
if (!currentOrg?.id) return;
@ -152,12 +174,28 @@ export const OrgMembersTable = ({ handlePopUpOpen, setCompleteInviteLink }: Prop
return (
<div>
<Input
value={searchMemberFilter}
onChange={(e) => setSearchMemberFilter(e.target.value)}
leftIcon={<FontAwesomeIcon icon={faMagnifyingGlass} />}
placeholder="Search members..."
/>
<div className="flex">
<Input
value={searchMemberFilter}
onChange={(e) => setSearchMemberFilter(e.target.value)}
leftIcon={<FontAwesomeIcon icon={faMagnifyingGlass} />}
placeholder="Search members..."
/>
<OrgPermissionCan I={OrgPermissionActions.Create} a={OrgPermissionSubjects.Member}>
{(isAllowed) => (
<Button
colorSchema="secondary"
type="submit"
leftIcon={<FontAwesomeIcon icon={faPlus} />}
onClick={() => handleAddMemberModal()}
isDisabled={!isAllowed}
className="ml-4"
>
Add member
</Button>
)}
</OrgPermissionCan>
</div>
<TableContainer className="mt-4">
<Table>
<THead>

View File

@ -1,10 +1,13 @@
import Link from "next/link";
import { faArrowUpRightFromSquare, faPlus } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
// import Link from "next/link";
// import { faArrowUpRightFromSquare, faPlus } from "@fortawesome/free-solid-svg-icons";
// import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider";
import { ProjectPermissionCan } from "@app/components/permissions";
import { Button, DeleteActionModal } from "@app/components/v2";
// import { ProjectPermissionCan } from "@app/components/permissions";
import {
// Button,
DeleteActionModal
} from "@app/components/v2";
import { ProjectPermissionActions, ProjectPermissionSub, useWorkspace } from "@app/context";
import { withProjectPermission } from "@app/hoc";
import { useDeleteIdentityFromWorkspace } from "@app/hooks/api";
@ -55,9 +58,10 @@ export const IdentitySection = withProjectPermission(
return (
<div className="mb-6 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
<div className="mb-4 flex items-center justify-between">
<p className="text-xl font-semibold text-mineshaft-100">Identities</p>
<div className="flex w-full justify-end pr-4">
<div className="py-4">
<p className="mb-2 text-md text-mineshaft-100">Machine Identities</p>
<p className="text-sm text-mineshaft-300">Manage which apps/services have access to this project</p>
{/* <div className="flex w-full justify-end pr-4">
<Link href="https://infisical.com/docs/documentation/platform/identities/overview">
<span className="w-max cursor-pointer rounded-md border border-mineshaft-500 bg-mineshaft-600 px-4 py-2 text-mineshaft-200 duration-200 hover:border-primary/40 hover:bg-primary/10 hover:text-white">
Documentation{" "}
@ -67,8 +71,8 @@ export const IdentitySection = withProjectPermission(
/>
</span>
</Link>
</div>
<ProjectPermissionCan
</div> */}
{/* <ProjectPermissionCan
I={ProjectPermissionActions.Create}
a={ProjectPermissionSub.Identity}
>
@ -83,7 +87,7 @@ export const IdentitySection = withProjectPermission(
Add identity
</Button>
)}
</ProjectPermissionCan>
</ProjectPermissionCan> */}
</div>
<IdentityTable handlePopUpOpen={handlePopUpOpen} />
<IdentityModal popUp={popUp} handlePopUpToggle={handlePopUpToggle} />

View File

@ -1,11 +1,14 @@
import { faServer, faXmark } from "@fortawesome/free-solid-svg-icons";
import { useState } from "react";
import { faMagnifyingGlass,faPlus, faServer, faXmark } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { format } from "date-fns";
import { ProjectPermissionCan } from "@app/components/permissions";
import {
Button,
EmptyState,
IconButton,
Input,
Table,
TableContainer,
TableSkeleton,
@ -33,76 +36,103 @@ type Props = {
export const IdentityTable = ({ handlePopUpOpen }: Props) => {
const { currentWorkspace } = useWorkspace();
const { data, isLoading } = useGetWorkspaceIdentityMemberships(currentWorkspace?.id || "");
const { data: identities, isLoading } = useGetWorkspaceIdentityMemberships(currentWorkspace?.id || "");
const [searchIdentity, setSearchIdentity] = useState("");
const filteredIdentities = identities ? identities.filter(({ identity: { name } }) => name.toLocaleLowerCase().includes(searchIdentity.toLocaleLowerCase())) : [];
return (
<TableContainer>
<Table>
<THead>
<Tr>
<Th>Name</Th>
<Th>Role</Th>
<Th>Added on</Th>
<Th className="w-5" />
</Tr>
</THead>
<TBody>
{isLoading && <TableSkeleton columns={7} innerKey="project-identities" />}
{!isLoading &&
data &&
data.length > 0 &&
data.map(({ identity: { id, name }, roles, createdAt }) => {
return (
<Tr className="h-10" key={`st-v3-${id}`}>
<Td>{name}</Td>
<Td>
<ProjectPermissionCan
I={ProjectPermissionActions.Edit}
a={ProjectPermissionSub.Identity}
>
{(isAllowed) => (
<IdentityRoles roles={roles} disableEdit={!isAllowed} identityId={id} />
)}
</ProjectPermissionCan>
</Td>
<Td>{format(new Date(createdAt), "yyyy-MM-dd")}</Td>
<Td className="flex justify-end">
<ProjectPermissionCan
I={ProjectPermissionActions.Delete}
a={ProjectPermissionSub.Identity}
>
{(isAllowed) => (
<IconButton
onClick={() => {
handlePopUpOpen("deleteIdentity", {
identityId: id,
name
});
}}
size="lg"
colorSchema="danger"
variant="plain"
ariaLabel="update"
className="ml-4"
isDisabled={!isAllowed}
>
<FontAwesomeIcon icon={faXmark} />
</IconButton>
)}
</ProjectPermissionCan>
</Td>
</Tr>
);
})}
{!isLoading && data && data?.length === 0 && (
<div>
<div className="flex">
<Input
value={searchIdentity}
onChange={(e) => setSearchIdentity(e.target.value)}
leftIcon={<FontAwesomeIcon icon={faMagnifyingGlass} />}
placeholder="Search identities..."
/>
<ProjectPermissionCan
I={ProjectPermissionActions.Create}
a={ProjectPermissionSub.Identity}
>
{(isAllowed) => (
<Button
colorSchema="secondary"
type="submit"
leftIcon={<FontAwesomeIcon icon={faPlus} />}
onClick={() => handlePopUpOpen("identity")}
isDisabled={!isAllowed}
className="ml-4"
>
Add identity
</Button>
)}
</ProjectPermissionCan>
</div>
<TableContainer className="mt-4">
<Table>
<THead>
<Tr>
<Td colSpan={7}>
<EmptyState title="No identities have been added to this project" icon={faServer} />
</Td>
<Th>Name</Th>
<Th>Role</Th>
<Th>Added on</Th>
<Th className="w-5" />
</Tr>
)}
</TBody>
</Table>
</TableContainer>
</THead>
<TBody>
{isLoading && <TableSkeleton columns={7} innerKey="project-identities" />}
{!isLoading && filteredIdentities?.map(({ identity: { id, name }, roles, createdAt }) => {
return (
<Tr className="h-10" key={`st-v3-${id}`}>
<Td>{name}</Td>
<Td>
<ProjectPermissionCan
I={ProjectPermissionActions.Edit}
a={ProjectPermissionSub.Identity}
>
{(isAllowed) => (
<IdentityRoles roles={roles} disableEdit={!isAllowed} identityId={id} />
)}
</ProjectPermissionCan>
</Td>
<Td>{format(new Date(createdAt), "yyyy-MM-dd")}</Td>
<Td className="flex justify-end">
<ProjectPermissionCan
I={ProjectPermissionActions.Delete}
a={ProjectPermissionSub.Identity}
>
{(isAllowed) => (
<IconButton
onClick={() => {
handlePopUpOpen("deleteIdentity", {
identityId: id,
name
});
}}
size="lg"
colorSchema="danger"
variant="plain"
ariaLabel="update"
className="ml-4"
isDisabled={!isAllowed}
>
<FontAwesomeIcon icon={faXmark} />
</IconButton>
)}
</ProjectPermissionCan>
</Td>
</Tr>
);
})}
</TBody>
</Table>
{!isLoading && filteredIdentities?.length === 0 && (
<EmptyState
title={searchIdentity === "" ? "No identities have been added to this project" : "No matching identities found"}
icon={faServer}
/>
)}
</TableContainer>
</div>
);
};

View File

@ -186,28 +186,32 @@ export const MemberListTab = () => {
return (
<div className="mb-6 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
<div className="mb-4 flex items-center justify-between">
<p className="text-xl font-semibold text-mineshaft-100">Members</p>
<div className="py-4">
<h2 className="mb-2 text-md text-mineshaft-100">Members</h2>
<p className="text-sm text-mineshaft-300">Manage who has access to this project</p>
</div>
<div className="flex">
<Input
value={searchMemberFilter}
onChange={(e) => setSearchMemberFilter(e.target.value)}
leftIcon={<FontAwesomeIcon icon={faMagnifyingGlass} />}
placeholder="Search members..."
/>
<ProjectPermissionCan I={ProjectPermissionActions.Create} a={ProjectPermissionSub.Member}>
{(isAllowed) => (
<Button
colorSchema="primary"
colorSchema="secondary"
type="submit"
leftIcon={<FontAwesomeIcon icon={faPlus} />}
onClick={() => handlePopUpOpen("addMember")}
isDisabled={!isAllowed}
className="ml-4"
>
Add Member
</Button>
)}
</ProjectPermissionCan>
</div>
<Input
value={searchMemberFilter}
onChange={(e) => setSearchMemberFilter(e.target.value)}
leftIcon={<FontAwesomeIcon icon={faMagnifyingGlass} />}
placeholder="Search members..."
/>
<div className="mt-4">
<TableContainer>
<Table>

View File

@ -1,9 +1,6 @@
import { useTranslation } from "react-i18next";
import { faPlus } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { OrgPermissionCan, PermissionDeniedBanner } from "@app/components/permissions";
import { Button } from "@app/components/v2";
import { PermissionDeniedBanner } from "@app/components/permissions";
import { OrgPermissionActions, OrgPermissionSubjects, useOrgPermission } from "@app/context";
import { usePopUp } from "@app/hooks";
@ -12,7 +9,7 @@ import { OrgIncidentContactsTable } from "./OrgIncidentContactsTable";
export const OrgIncidentContactsSection = () => {
const { t } = useTranslation();
const { handlePopUpToggle, popUp, handlePopUpOpen, handlePopUpClose } = usePopUp([
const { handlePopUpToggle, popUp, handlePopUpClose } = usePopUp([
"addContact"
] as const);
const { permission } = useOrgPermission();
@ -20,22 +17,7 @@ export const OrgIncidentContactsSection = () => {
return (
<>
<hr className="border-mineshaft-600" />
<div className="flex items-center justify-between pt-4">
<p className="text-md text-mineshaft-100">{t("section.incident.incident-contacts")}</p>
<OrgPermissionCan I={OrgPermissionActions.Create} a={OrgPermissionSubjects.IncidentAccount}>
{(isAllowed) => (
<Button
colorSchema="secondary"
type="submit"
isDisabled={!isAllowed}
leftIcon={<FontAwesomeIcon icon={faPlus} />}
onClick={() => handlePopUpOpen("addContact")}
>
Add contact
</Button>
)}
</OrgPermissionCan>
</div>
<p className="pt-4 text-md text-mineshaft-100">{t("section.incident.incident-contacts")}</p>
<div className="py-4">
{permission.can(OrgPermissionActions.Read, OrgPermissionSubjects.IncidentAccount) ? (
<OrgIncidentContactsTable />

View File

@ -1,10 +1,11 @@
import { useState } from "react";
import { faContactBook, faMagnifyingGlass, faTrash } from "@fortawesome/free-solid-svg-icons";
import { faContactBook, faMagnifyingGlass, faPlus, faXmark } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider";
import { OrgPermissionCan } from "@app/components/permissions";
import {
Button,
DeleteActionModal,
EmptyState,
IconButton,
@ -16,12 +17,13 @@ import {
Td,
Th,
THead,
Tr
} from "@app/components/v2";
Tr} from "@app/components/v2";
import { OrgPermissionActions, OrgPermissionSubjects, useOrganization } from "@app/context";
import { usePopUp } from "@app/hooks";
import { useDeleteIncidentContact, useGetOrgIncidentContact } from "@app/hooks/api";
import { AddOrgIncidentContactModal } from "./AddOrgIncidentContactModal";
export const OrgIncidentContactsTable = () => {
const { createNotification } = useNotificationContext();
const { currentOrg } = useOrganization();
@ -29,7 +31,8 @@ export const OrgIncidentContactsTable = () => {
const [searchContact, setSearchContact] = useState("");
const { handlePopUpToggle, popUp, handlePopUpOpen, handlePopUpClose } = usePopUp([
"removeContact",
"setUpEmail"
"setUpEmail",
"addContact"
] as const);
const { mutateAsync } = useDeleteIncidentContact();
@ -64,12 +67,28 @@ export const OrgIncidentContactsTable = () => {
return (
<div>
<Input
value={searchContact}
onChange={(e) => setSearchContact(e.target.value)}
leftIcon={<FontAwesomeIcon icon={faMagnifyingGlass} />}
placeholder="Search incident contact by email..."
/>
<div className=" flex">
<Input
value={searchContact}
onChange={(e) => setSearchContact(e.target.value)}
leftIcon={<FontAwesomeIcon icon={faMagnifyingGlass} />}
placeholder="Search incident contact by email..."
/>
<OrgPermissionCan I={OrgPermissionActions.Create} a={OrgPermissionSubjects.IncidentAccount}>
{(isAllowed) => (
<Button
colorSchema="secondary"
type="submit"
isDisabled={!isAllowed}
leftIcon={<FontAwesomeIcon icon={faPlus} />}
onClick={() => handlePopUpOpen("addContact")}
className="ml-4"
>
Add contact
</Button>
)}
</OrgPermissionCan>
</div>
<TableContainer className="mt-4">
<Table>
<THead>
@ -90,12 +109,14 @@ export const OrgIncidentContactsTable = () => {
>
{(isAllowed) => (
<IconButton
ariaLabel="delete"
colorSchema="danger"
onClick={() => handlePopUpOpen("removeContact", { email, id })}
size="lg"
colorSchema="danger"
variant="plain"
ariaLabel="delete"
isDisabled={!isAllowed}
>
<FontAwesomeIcon icon={faTrash} />
<FontAwesomeIcon icon={faXmark} />
</IconButton>
)}
</OrgPermissionCan>
@ -115,6 +136,11 @@ export const OrgIncidentContactsTable = () => {
onChange={(isOpen) => handlePopUpToggle("removeContact", isOpen)}
onDeleteApproved={onRemoveIncidentContact}
/>
<AddOrgIncidentContactModal
popUp={popUp}
handlePopUpClose={handlePopUpClose}
handlePopUpToggle={handlePopUpToggle}
/>
</div>
);
};

View File

@ -62,7 +62,7 @@ export const OrgNameChangeSection = (): JSX.Element => {
return (
<form onSubmit={handleSubmit(onFormSubmit)} className="py-4">
<div className="">
<div>
<h2 className="mb-2 text-md text-mineshaft-100">Organization Name</h2>
<Controller
defaultValue=""

View File

@ -1,49 +1,16 @@
import { Fragment } from "react";
import { useTranslation } from "react-i18next";
import { Tab } from "@headlessui/react";
import { ProjectGeneralTab } from "./components/ProjectGeneralTab";
import { WebhooksTab } from "./components/WebhooksTab";
const tabs = [
{ name: "General", key: "tab-project-general" },
{ name: "Webhooks", key: "tab-project-webhooks" }
];
import { ProjectTabGroup } from "./components";
export const ProjectSettingsPage = () => {
const { t } = useTranslation();
return (
<div className="flex justify-center w-full h-full bg-bunker-800 text-white">
<div className="max-w-7xl px-6 w-full">
<div className="max-w-4xl px-6 w-full">
<div className="my-6">
<p className="text-3xl font-semibold text-gray-200">{t("settings.project.title")}</p>
</div>
<Tab.Group>
<Tab.List className="mb-4 w-full border-b-2 border-mineshaft-800">
{tabs.map((tab) => (
<Tab as={Fragment} key={tab.key}>
{({ selected }) => (
<button
type="button"
className={`w-30 py-2 mx-2 mr-4 font-medium text-sm outline-none ${
selected ? "border-b border-white text-white" : "text-mineshaft-400"
}`}
>
{tab.name}
</button>
)}
</Tab>
))}
</Tab.List>
<Tab.Panels>
<Tab.Panel>
<ProjectGeneralTab />
</Tab.Panel>
<Tab.Panel>
<WebhooksTab />
</Tab.Panel>
</Tab.Panels>
</Tab.Group>
<ProjectTabGroup />
</div>
</div>
);

View File

@ -36,25 +36,28 @@ export const AutoCapitalizationSection = () => {
};
return (
<div className="mb-6 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
<p className="mb-3 text-xl font-semibold">{t("settings.project.auto-capitalization")}</p>
<ProjectPermissionCan I={ProjectPermissionActions.Edit} a={ProjectPermissionSub.Settings}>
{(isAllowed) => (
<div className="w-max">
<Checkbox
className="data-[state=checked]:bg-primary"
id="autoCapitalization"
isDisabled={!isAllowed}
isChecked={currentWorkspace?.autoCapitalization ?? false}
onCheckedChange={(state) => {
handleToggleCapitalizationToggle(state as boolean);
}}
>
{t("settings.project.auto-capitalization-description")}
</Checkbox>
</div>
)}
</ProjectPermissionCan>
</div>
<>
<hr className="border-mineshaft-600" />
<div className="pt-4">
<p className="text-md text-mineshaft-100">{t("settings.project.auto-capitalization")}</p>
<ProjectPermissionCan I={ProjectPermissionActions.Edit} a={ProjectPermissionSub.Settings}>
{(isAllowed) => (
<div className="w-max py-4">
<Checkbox
className="data-[state=checked]:bg-primary"
id="autoCapitalization"
isDisabled={!isAllowed}
isChecked={currentWorkspace?.autoCapitalization ?? false}
onCheckedChange={(state) => {
handleToggleCapitalizationToggle(state as boolean);
}}
>
{t("settings.project.auto-capitalization-description")}
</Checkbox>
</div>
)}
</ProjectPermissionCan>
</div>
</>
);
};

View File

@ -54,22 +54,25 @@ export const DeleteProjectSection = () => {
};
return (
<div className="mb-6 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
<p className="mb-4 text-xl font-semibold text-mineshaft-100">Danger Zone</p>
<ProjectPermissionCan I={ProjectPermissionActions.Delete} a={ProjectPermissionSub.Workspace}>
{(isAllowed) => (
<Button
isLoading={isDeleting}
isDisabled={!isAllowed || isDeleting}
colorSchema="danger"
variant="outline_bg"
type="submit"
onClick={() => handlePopUpOpen("deleteWorkspace")}
>
{`Delete ${currentWorkspace?.name}`}
</Button>
)}
</ProjectPermissionCan>
<>
<hr className="border-mineshaft-600" />
<div className="py-4">
<p className="mb-4 text-md text-mineshaft-100">Danger Zone</p>
<ProjectPermissionCan I={ProjectPermissionActions.Delete} a={ProjectPermissionSub.Workspace}>
{(isAllowed) => (
<Button
isLoading={isDeleting}
isDisabled={!isAllowed || isDeleting}
colorSchema="danger"
variant="outline_bg"
type="submit"
onClick={() => handlePopUpOpen("deleteWorkspace")}
>
{`Delete ${currentWorkspace?.name}`}
</Button>
)}
</ProjectPermissionCan>
</div>
<DeleteActionModal
isOpen={popUp.deleteWorkspace.isOpen}
title="Are you sure want to delete this project?"
@ -79,6 +82,6 @@ export const DeleteProjectSection = () => {
buttonText="Delete Project"
onDeleteApproved={handleDeleteWorkspaceSubmit}
/>
</div>
</>
);
};

View File

@ -1,9 +1,10 @@
import { faPlus } from "@fortawesome/free-solid-svg-icons";
import { useState } from "react";
import { faMagnifyingGlass,faPlus } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider";
import { PermissionDeniedBanner, ProjectPermissionCan } from "@app/components/permissions";
import { Button, DeleteActionModal, UpgradePlanModal } from "@app/components/v2";
import { Button, DeleteActionModal, Input, UpgradePlanModal } from "@app/components/v2";
import {
ProjectPermissionActions,
ProjectPermissionSub,
@ -18,11 +19,15 @@ import { AddEnvironmentModal } from "./AddEnvironmentModal";
import { EnvironmentTable } from "./EnvironmentTable";
import { UpdateEnvironmentModal } from "./UpdateEnvironmentModal";
// TODO: resolve strange subtitle spacing / design
// TODO: resolve filtering stuff
export const EnvironmentSection = () => {
const { createNotification } = useNotificationContext();
const { subscription } = useSubscription();
const { currentWorkspace } = useWorkspace();
const { permission } = useProjectPermission();
const [searchEnv, setSearchEnv] = useState("");
const deleteWsEnvironment = useDeleteWsEnvironment();
@ -63,14 +68,21 @@ export const EnvironmentSection = () => {
};
return (
<div
id="environments"
className="mb-6 scroll-m-6 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4"
>
<div className="mb-8 flex justify-between">
<p className="text-xl font-semibold text-mineshaft-100">Environments</p>
<div>
<ProjectPermissionCan
<div id="environments">
<hr className="border-mineshaft-600" />
<p className="pt-4 text-md text-mineshaft-100">Environments</p>
<p className="pt-4 text-sm text-mineshaft-300">
Choose which environments will show up in your dashboard like development, staging,
production
</p>
<div className="flex pt-4">
<Input
value={searchEnv}
onChange={(e) => setSearchEnv(e.target.value)}
leftIcon={<FontAwesomeIcon icon={faMagnifyingGlass} />}
placeholder="Search environments by name/slug..."
/>
<ProjectPermissionCan
I={ProjectPermissionActions.Create}
a={ProjectPermissionSub.Environments}
>
@ -86,22 +98,20 @@ export const EnvironmentSection = () => {
}
}}
isDisabled={!isAllowed}
className="ml-4"
>
Create environment
Create
</Button>
)}
</ProjectPermissionCan>
</div>
</div>
<p className="mb-8 text-gray-400">
Choose which environments will show up in your dashboard like development, staging,
production
</p>
{permission.can(ProjectPermissionActions.Read, ProjectPermissionSub.Environments) ? (
<EnvironmentTable handlePopUpOpen={handlePopUpOpen} />
) : (
<PermissionDeniedBanner />
)}
<div className="py-4">
{permission.can(ProjectPermissionActions.Read, ProjectPermissionSub.Environments) ? (
<EnvironmentTable handlePopUpOpen={handlePopUpOpen} />
) : (
<PermissionDeniedBanner />
)}
</div>
<AddEnvironmentModal
popUp={popUp}
handlePopUpClose={handlePopUpClose}

View File

@ -7,7 +7,7 @@ import { SecretTagsSection } from "../SecretTagsSection";
export const ProjectGeneralTab = () => {
return (
<div>
<div className="rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-6">
<ProjectNameChangeSection />
<EnvironmentSection />
<SecretTagsSection />

View File

@ -9,10 +9,15 @@ import { Button, FormControl, Input } from "@app/components/v2";
import { ProjectPermissionActions, ProjectPermissionSub, useWorkspace } from "@app/context";
import { useRenameWorkspace } from "@app/hooks/api";
import { CopyButton } from "./CopyButton";
// import { CopyButton } from "./CopyButton";
const formSchema = yup.object({
name: yup.string().required().label("Project Name").max(64, "Too long, maximum length is 64 characters"),
slug: yup
.string()
.matches(/^[a-zA-Z0-9-]+$/, "Name must only contain alphanumeric characters or hyphens")
.required()
.label("Project Slug")
});
type FormData = yup.InferType<typeof formSchema>;
@ -27,7 +32,8 @@ export const ProjectNameChangeSection = () => {
useEffect(() => {
if (currentWorkspace) {
reset({
name: currentWorkspace.name
name: currentWorkspace.name,
slug: currentWorkspace.slug
});
}
}, [currentWorkspace]);
@ -55,11 +61,47 @@ export const ProjectNameChangeSection = () => {
};
return (
<form
onSubmit={handleSubmit(onFormSubmit)}
className="mb-6 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4"
>
<div className="justify-betweens flex">
<form onSubmit={handleSubmit(onFormSubmit)} className="py-4">
<div>
<h2 className="mb-2 text-md text-mineshaft-100">Project Name</h2>
<Controller
defaultValue=""
render={({ field, fieldState: { error } }) => (
<FormControl isError={Boolean(error)} errorText={error?.message} className="max-w-md">
<Input placeholder="Project Echo" {...field} />
</FormControl>
)}
control={control}
name="name"
/>
</div>
{/* <div className="py-4">
<h2 className="mb-2 text-md text-mineshaft-100">Project Slug</h2>
<Controller
defaultValue=""
render={({ field, fieldState: { error } }) => (
<FormControl isError={Boolean(error)} errorText={error?.message} className="max-w-md">
<Input placeholder="echo" {...field} />
</FormControl>
)}
control={control}
name="slug"
/>
</div> */}
{/* <div className="py-4">
<h2 className="mb-2 text-md text-mineshaft-100">Project ID</h2>
<Controller
defaultValue=""
render={({ field, fieldState: { error } }) => (
<FormControl isError={Boolean(error)} errorText={error?.message} className="max-w-md">
<Input placeholder="echo" {...field} />
</FormControl>
)}
control={control}
name="slug"
/>
</div> */}
{/* <div className="justify-betweens flex">
<h2 className="mb-8 flex-1 text-xl font-semibold text-mineshaft-100">Project Name</h2>
<div className="space-x-2">
<CopyButton
@ -77,8 +119,8 @@ export const ProjectNameChangeSection = () => {
Copy Project ID
</CopyButton>
</div>
</div>
<div className="max-w-md">
</div> */}
{/* <div className="max-w-md">
<ProjectPermissionCan I={ProjectPermissionActions.Edit} a={ProjectPermissionSub.Workspace}>
{(isAllowed) => (
<Controller
@ -98,7 +140,7 @@ export const ProjectNameChangeSection = () => {
/>
)}
</ProjectPermissionCan>
</div>
</div> */}
<ProjectPermissionCan I={ProjectPermissionActions.Edit} a={ProjectPermissionSub.Workspace}>
{(isAllowed) => (
<Button

View File

@ -0,0 +1,41 @@
import { Fragment } from "react";
import { Tab } from "@headlessui/react";
import { ProjectGeneralTab } from "../ProjectGeneralTab";
import { WebhooksTab } from "../WebhooksTab";
const tabs = [
{ name: "General", key: "tab-project-general" },
{ name: "Webhooks", key: "tab-project-webhooks" }
];
export const ProjectTabGroup = () => {
return (
<Tab.Group>
<Tab.List className="mb-4 w-full border-b-2 border-mineshaft-800">
{tabs.map((tab) => (
<Tab as={Fragment} key={tab.key}>
{({ selected }) => (
<button
type="button"
className={`w-30 py-2 mx-2 mr-4 font-medium text-sm outline-none ${
selected ? "border-b border-white text-white" : "text-mineshaft-400"
}`}
>
{tab.name}
</button>
)}
</Tab>
))}
</Tab.List>
<Tab.Panels>
<Tab.Panel>
<ProjectGeneralTab />
</Tab.Panel>
<Tab.Panel>
<WebhooksTab />
</Tab.Panel>
</Tab.Panels>
</Tab.Group>
);
}

View File

@ -0,0 +1 @@
export { ProjectTabGroup } from "./ProjectTabGroup";

View File

@ -1,9 +1,23 @@
import { faPlus } from "@fortawesome/free-solid-svg-icons";
import { useState } from "react";
import { faMagnifyingGlass, faPlus, faTags,faXmark } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider";
import { PermissionDeniedBanner, ProjectPermissionCan } from "@app/components/permissions";
import { Button, DeleteActionModal } from "@app/components/v2";
import {
Button,
DeleteActionModal,
EmptyState,
IconButton,
Input,
Table,
TableContainer,
TableSkeleton,
TBody,
Td,
Th,
THead,
Tr} from "@app/components/v2";
import {
ProjectPermissionActions,
ProjectPermissionSub,
@ -11,10 +25,12 @@ import {
useWorkspace
} from "@app/context";
import { usePopUp } from "@app/hooks";
import { useDeleteWsTag } from "@app/hooks/api";
import {
useDeleteWsTag,
useGetWsTags
} from "@app/hooks/api";
import { AddSecretTagModal } from "./AddSecretTagModal";
import { SecretTagsTable } from "./SecretTagsTable";
type DeleteModalData = { name: string; id: string };
@ -25,9 +41,10 @@ export const SecretTagsSection = (): JSX.Element => {
"deleteTagConfirmation"
] as const);
const { currentWorkspace } = useWorkspace();
const { data: tags, isLoading } = useGetWsTags(currentWorkspace?.id ?? "");
const { permission } = useProjectPermission();
const deleteWsTag = useDeleteWsTag();
const [searchTag, setSearchTag] = useState("");
const onDeleteApproved = async () => {
try {
@ -51,10 +68,28 @@ export const SecretTagsSection = (): JSX.Element => {
}
};
const filteredTags = tags
? tags.filter(({ name }) => name.toLocaleLowerCase().includes(searchTag.toLocaleLowerCase()))
: [];
return (
<div className="mb-6 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
<div className="mb-8 flex justify-between">
<p className="mb-3 text-xl font-semibold">Secret Tags</p>
<div>
<hr className="border-mineshaft-600" />
<div className="flex items-center justify-between pt-4">
<p className="text-md text-mineshaft-100">Secret Tags</p>
</div>
<p className="pt-4 text-sm text-mineshaft-300">
Every secret can be assigned to one or more tags. Here you can add and remove tags for the
current project.
</p>
<div className="pt-4 flex">
<Input
value={searchTag}
onChange={(e) => setSearchTag(e.target.value)}
leftIcon={<FontAwesomeIcon icon={faMagnifyingGlass} />}
placeholder="Search tags by name/slug..."
/>
<ProjectPermissionCan I={ProjectPermissionActions.Create} a={ProjectPermissionSub.Tags}>
{(isAllowed) => (
<Button
@ -64,21 +99,67 @@ export const SecretTagsSection = (): JSX.Element => {
handlePopUpOpen("CreateSecretTag");
}}
isDisabled={!isAllowed}
className="ml-4"
>
Create tag
Create
</Button>
)}
</ProjectPermissionCan>
</div>
<p className="mb-8 text-gray-400">
Every secret can be assigned to one or more tags. Here you can add and remove tags for the
current project.
</p>
{permission.can(ProjectPermissionActions.Read, ProjectPermissionSub.Tags) ? (
<SecretTagsTable handlePopUpOpen={handlePopUpOpen} />
) : (
<PermissionDeniedBanner />
<div className="py-4">
{permission.can(ProjectPermissionActions.Read, ProjectPermissionSub.Tags) ? (
<TableContainer>
<Table>
<THead>
<Tr>
<Th>Name</Th>
<Th>Slug</Th>
<Th aria-label="button" />
</Tr>
</THead>
<TBody>
{isLoading && <TableSkeleton columns={3} innerKey="secret-tags" />}
{filteredTags?.map(({ id, name, slug }) => (
<Tr key={name}>
<Td>{name}</Td>
<Td>{slug}</Td>
<Td className="flex items-center justify-end">
<ProjectPermissionCan
I={ProjectPermissionActions.Delete}
a={ProjectPermissionSub.Tags}
>
{(isAllowed) => (
<IconButton
onClick={() =>
handlePopUpOpen("deleteTagConfirmation", {
name,
id
})
}
size="lg"
colorSchema="danger"
variant="plain"
ariaLabel="update"
isDisabled={!isAllowed}
>
<FontAwesomeIcon icon={faXmark} />
</IconButton>
)}
</ProjectPermissionCan>
</Td>
</Tr>
))}
</TBody>
</Table>
{!isLoading && filteredTags?.length === 0 && (
<EmptyState title="No secret tags found" icon={faTags} />
)}
</TableContainer>
) : (
<PermissionDeniedBanner />
)}
</div>
<AddSecretTagModal
popUp={popUp}
handlePopUpClose={handlePopUpClose}

View File

@ -1,91 +0,0 @@
import { faTags, faTrashCan } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { ProjectPermissionCan } from "@app/components/permissions";
import {
EmptyState,
IconButton,
Table,
TableContainer,
TableSkeleton,
TBody,
Td,
Th,
THead,
Tr
} from "@app/components/v2";
import { ProjectPermissionActions, ProjectPermissionSub, useWorkspace } from "@app/context";
import { useGetWsTags } from "@app/hooks/api";
import { UsePopUpState } from "@app/hooks/usePopUp";
type Props = {
handlePopUpOpen: (
popUpName: keyof UsePopUpState<["deleteTagConfirmation"]>,
{
name,
id
}: {
name: string;
id: string;
}
) => void;
};
export const SecretTagsTable = ({ handlePopUpOpen }: Props) => {
const { currentWorkspace } = useWorkspace();
const { data, isLoading } = useGetWsTags(currentWorkspace?.id ?? "");
return (
<TableContainer className="mt-4">
<Table>
<THead>
<Tr>
<Th>Tag</Th>
<Th>Slug</Th>
<Th aria-label="button" />
</Tr>
</THead>
<TBody>
{isLoading && <TableSkeleton columns={3} innerKey="secret-tags" />}
{!isLoading &&
data &&
data.map(({ id, name, slug }) => (
<Tr key={name}>
<Td>{name}</Td>
<Td>{slug}</Td>
<Td className="flex items-center justify-end">
<ProjectPermissionCan
I={ProjectPermissionActions.Delete}
a={ProjectPermissionSub.Tags}
>
{(isAllowed) => (
<IconButton
onClick={() =>
handlePopUpOpen("deleteTagConfirmation", {
name,
id
})
}
colorSchema="danger"
ariaLabel="update"
isDisabled={!isAllowed}
>
<FontAwesomeIcon icon={faTrashCan} />
</IconButton>
)}
</ProjectPermissionCan>
</Td>
</Tr>
))}
{!isLoading && data && data?.length === 0 && (
<Tr>
<Td colSpan={3}>
<EmptyState title="No secret tags found" icon={faTags} />
</Td>
</Tr>
)}
</TBody>
</Table>
</TableContainer>
);
};

View File

@ -139,7 +139,7 @@ export const WebhooksTab = withProjectPermission(
};
return (
<div className="mb-6 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
<div className="rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-6">
<div className="flex justify-between">
<p className="text-xl font-semibold text-mineshaft-100">{t("settings.webhooks.title")}</p>
<ProjectPermissionCan

View File

@ -3,4 +3,5 @@ export { DeleteProjectSection } from "./DeleteProjectSection";
export { E2EESection } from "./E2EESection";
export { EnvironmentSection } from "./EnvironmentSection";
export { ProjectNameChangeSection } from "./ProjectNameChangeSection";
export { ProjectTabGroup } from "./ProjectTabGroup";
export { SecretTagsSection } from "./SecretTagsSection";