mirror of
https://github.com/Infisical/infisical.git
synced 2025-07-11 12:11:38 +00:00
Compare commits
4 Commits
daniel/go-
...
daniel/ope
Author | SHA1 | Date | |
---|---|---|---|
e3a356cda9 | |||
7fb3076238 | |||
a0865cda2e | |||
1e7b1ccf22 |
@ -95,10 +95,6 @@ RUN mkdir frontend-build
|
||||
# Production stage
|
||||
FROM base AS production
|
||||
RUN apk add --upgrade --no-cache ca-certificates
|
||||
RUN apk add --no-cache bash curl && curl -1sLf \
|
||||
'https://dl.cloudsmith.io/public/infisical/infisical-cli/setup.alpine.sh' | bash \
|
||||
&& apk add infisical=0.31.1 && apk add --no-cache git
|
||||
|
||||
RUN addgroup --system --gid 1001 nodejs \
|
||||
&& adduser --system --uid 1001 non-root-user
|
||||
|
||||
|
@ -2,8 +2,6 @@ import { z } from "zod";
|
||||
|
||||
import { GitAppOrgSchema, SecretScanningGitRisksSchema } from "@app/db/schemas";
|
||||
import { SecretScanningRiskStatus } from "@app/ee/services/secret-scanning/secret-scanning-types";
|
||||
import { getConfig } from "@app/lib/config/env";
|
||||
import { BadRequestError } from "@app/lib/errors";
|
||||
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
||||
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||
import { AuthMode } from "@app/services/auth/auth-type";
|
||||
@ -25,13 +23,6 @@ export const registerSecretScanningRouter = async (server: FastifyZodProvider) =
|
||||
},
|
||||
onRequest: verifyAuth([AuthMode.JWT]),
|
||||
handler: async (req) => {
|
||||
const appCfg = getConfig();
|
||||
if (!appCfg.SECRET_SCANNING_ORG_WHITELIST?.includes(req.auth.orgId)) {
|
||||
throw new BadRequestError({
|
||||
message: "Secret scanning is temporarily unavailable."
|
||||
});
|
||||
}
|
||||
|
||||
const session = await server.services.secretScanning.createInstallationSession({
|
||||
actor: req.permission.type,
|
||||
actorId: req.permission.id,
|
||||
@ -39,7 +30,6 @@ export const registerSecretScanningRouter = async (server: FastifyZodProvider) =
|
||||
actorOrgId: req.permission.orgId,
|
||||
orgId: req.body.organizationId
|
||||
});
|
||||
|
||||
return session;
|
||||
}
|
||||
});
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { ProbotOctokit } from "probot";
|
||||
|
||||
import { OrgMembershipRole, TableName } from "@app/db/schemas";
|
||||
import { OrgMembershipRole } from "@app/db/schemas";
|
||||
import { getConfig } from "@app/lib/config/env";
|
||||
import { logger } from "@app/lib/logger";
|
||||
import { QueueJobs, QueueName, TQueueServiceFactory } from "@app/queue";
|
||||
@ -61,7 +61,7 @@ export const secretScanningQueueFactory = ({
|
||||
const getOrgAdminEmails = async (organizationId: string) => {
|
||||
// get emails of admins
|
||||
const adminsOfWork = await orgMemberDAL.findMembership({
|
||||
[`${TableName.Organization}.id` as string]: organizationId,
|
||||
orgId: organizationId,
|
||||
role: OrgMembershipRole.Admin
|
||||
});
|
||||
return adminsOfWork.filter((userObject) => userObject.email).map((userObject) => userObject.email as string);
|
||||
|
@ -90,7 +90,7 @@ export const secretScanningServiceFactory = ({
|
||||
const {
|
||||
data: { repositories }
|
||||
} = await octokit.apps.listReposAccessibleToInstallation();
|
||||
if (appCfg.SECRET_SCANNING_ORG_WHITELIST?.includes(actorOrgId)) {
|
||||
if (!appCfg.DISABLE_SECRET_SCANNING) {
|
||||
await Promise.all(
|
||||
repositories.map(({ id, full_name }) =>
|
||||
secretScanningQueue.startFullRepoScan({
|
||||
@ -164,7 +164,7 @@ export const secretScanningServiceFactory = ({
|
||||
});
|
||||
if (!installationLink) return;
|
||||
|
||||
if (appCfg.SECRET_SCANNING_ORG_WHITELIST?.includes(installationLink.orgId)) {
|
||||
if (!appCfg.DISABLE_SECRET_SCANNING) {
|
||||
await secretScanningQueue.startPushEventScan({
|
||||
commits,
|
||||
pusher: { name: pusher.name, email: pusher.email },
|
||||
|
@ -142,7 +142,6 @@ const envSchema = z
|
||||
SECRET_SCANNING_WEBHOOK_SECRET: zpStr(z.string().optional()),
|
||||
SECRET_SCANNING_GIT_APP_ID: zpStr(z.string().optional()),
|
||||
SECRET_SCANNING_PRIVATE_KEY: zpStr(z.string().optional()),
|
||||
SECRET_SCANNING_ORG_WHITELIST: zpStr(z.string().optional()),
|
||||
// LICENSE
|
||||
LICENSE_SERVER_URL: zpStr(z.string().optional().default("https://portal.infisical.com")),
|
||||
LICENSE_SERVER_KEY: zpStr(z.string().optional()),
|
||||
@ -178,8 +177,7 @@ const envSchema = z
|
||||
Boolean(data.SECRET_SCANNING_GIT_APP_ID) &&
|
||||
Boolean(data.SECRET_SCANNING_PRIVATE_KEY) &&
|
||||
Boolean(data.SECRET_SCANNING_WEBHOOK_SECRET),
|
||||
samlDefaultOrgSlug: data.DEFAULT_SAML_ORG_SLUG,
|
||||
SECRET_SCANNING_ORG_WHITELIST: data.SECRET_SCANNING_ORG_WHITELIST?.split(",")
|
||||
samlDefaultOrgSlug: data.DEFAULT_SAML_ORG_SLUG
|
||||
}));
|
||||
|
||||
let envCfg: Readonly<z.infer<typeof envSchema>>;
|
||||
|
@ -71,13 +71,6 @@ export class BadRequestError extends Error {
|
||||
}
|
||||
}
|
||||
|
||||
export class RateLimitError extends Error {
|
||||
constructor({ message }: { message?: string }) {
|
||||
super(message || "Rate limit exceeded");
|
||||
this.name = "RateLimitExceeded";
|
||||
}
|
||||
}
|
||||
|
||||
export class NotFoundError extends Error {
|
||||
name: string;
|
||||
|
||||
|
@ -2,7 +2,6 @@ import type { RateLimitOptions, RateLimitPluginOptions } from "@fastify/rate-lim
|
||||
import { Redis } from "ioredis";
|
||||
|
||||
import { getConfig } from "@app/lib/config/env";
|
||||
import { RateLimitError } from "@app/lib/errors";
|
||||
|
||||
export const globalRateLimiterCfg = (): RateLimitPluginOptions => {
|
||||
const appCfg = getConfig();
|
||||
@ -11,11 +10,6 @@ export const globalRateLimiterCfg = (): RateLimitPluginOptions => {
|
||||
: null;
|
||||
|
||||
return {
|
||||
errorResponseBuilder: (_, context) => {
|
||||
throw new RateLimitError({
|
||||
message: `Rate limit exceeded. Please try again in ${context.after}`
|
||||
});
|
||||
},
|
||||
timeWindow: 60 * 1000,
|
||||
max: 600,
|
||||
redis,
|
||||
|
@ -10,7 +10,6 @@ import {
|
||||
GatewayTimeoutError,
|
||||
InternalServerError,
|
||||
NotFoundError,
|
||||
RateLimitError,
|
||||
ScimRequestError,
|
||||
UnauthorizedError
|
||||
} from "@app/lib/errors";
|
||||
@ -28,8 +27,7 @@ enum HttpStatusCodes {
|
||||
Forbidden = 403,
|
||||
// eslint-disable-next-line @typescript-eslint/no-shadow
|
||||
InternalServerError = 500,
|
||||
GatewayTimeout = 504,
|
||||
TooManyRequests = 429
|
||||
GatewayTimeout = 504
|
||||
}
|
||||
|
||||
export const fastifyErrHandler = fastifyPlugin(async (server: FastifyZodProvider) => {
|
||||
@ -71,12 +69,6 @@ export const fastifyErrHandler = fastifyPlugin(async (server: FastifyZodProvider
|
||||
message: error.message,
|
||||
error: error.name
|
||||
});
|
||||
} else if (error instanceof RateLimitError) {
|
||||
void res.status(HttpStatusCodes.TooManyRequests).send({
|
||||
statusCode: HttpStatusCodes.TooManyRequests,
|
||||
message: error.message,
|
||||
error: error.name
|
||||
});
|
||||
} else if (error instanceof ScimRequestError) {
|
||||
void res.status(error.status).send({
|
||||
schemas: error.schemas,
|
||||
|
@ -225,7 +225,9 @@ export const registerRoutes = async (
|
||||
}: { auditLogDb?: Knex; db: Knex; smtp: TSmtpService; queue: TQueueServiceFactory; keyStore: TKeyStoreFactory }
|
||||
) => {
|
||||
const appCfg = getConfig();
|
||||
await server.register(registerSecretScannerGhApp, { prefix: "/ss-webhook" });
|
||||
if (!appCfg.DISABLE_SECRET_SCANNING) {
|
||||
await server.register(registerSecretScannerGhApp, { prefix: "/ss-webhook" });
|
||||
}
|
||||
|
||||
// db layers
|
||||
const userDAL = userDALFactory(db);
|
||||
|
@ -16,10 +16,8 @@ as well as create a new project.
|
||||
|
||||
The **Settings** page lets you manage information about your organization including:
|
||||
|
||||
- **Name**: The name of your organization.
|
||||
- **Slug**: The slug of your organization.
|
||||
- **Default Organization Member Role**: The role assigned to users when joining your organization unless otherwise specified.
|
||||
- **Incident Contacts**: Emails that should be alerted if anything abnormal is detected within the organization.
|
||||
- Name: The name of your organization.
|
||||
- Incident contacts: Emails that should be alerted if anything abnormal is detected within the organization.
|
||||
|
||||

|
||||
|
||||
|
@ -28,13 +28,6 @@ Prerequisites:
|
||||
|
||||

|
||||
</Step>
|
||||
<Step title="Add Users and Groups in Azure">
|
||||
In Azure, navigate to Enterprise Application > Users and Groups. Add any users and/or groups to your application that you would like
|
||||
to be provisioned over to Infisical.
|
||||
|
||||

|
||||
|
||||
</Step>
|
||||
<Step title="Configure SCIM in Azure">
|
||||
In Azure, head to your Enterprise Application > Provisioning > Overview and press **Get started**.
|
||||
|
||||
|
@ -1,26 +0,0 @@
|
||||
---
|
||||
title: "SCIM Group Mappings"
|
||||
description: "Learn how to enhance your SCIM implementation using group mappings"
|
||||
---
|
||||
|
||||
<Info>
|
||||
SCIM provisioning, and by extension group mapping, is a paid feature.
|
||||
|
||||
If you're using Infisical Cloud, then it is available under the **Enterprise Tier**. If you're self-hosting Infisical,
|
||||
then you should contact sales@infisical.com to purchase an enterprise license to use it.
|
||||
</Info>
|
||||
|
||||
## SCIM Group to Organization Role Mapping
|
||||
|
||||
By default, when users are provisioned via SCIM, they will be assigned the default organization role configured in [Organization General Settings](/documentation/platform/organization#settings).
|
||||
|
||||
For more precise control over membership roles, you can set up SCIM Group to Organization Role Mappings. This enables you to assign specific roles based on the group from which a user is provisioned.
|
||||
|
||||

|
||||
|
||||
To configure a mapping, simply enter the SCIM group's name and select the role you would like users to be assigned from this group. Be sure
|
||||
to tap **Update Mappings** once complete.
|
||||
|
||||
<Note>
|
||||
SCIM Group Mappings only apply when users are first provisioned. Previously provisioned users will not be affected, allowing you to customize user roles after they are added.
|
||||
</Note>
|
Binary file not shown.
Before Width: | Height: | Size: 985 KiB After Width: | Height: | Size: 1.2 MiB |
Binary file not shown.
Before Width: | Height: | Size: 523 KiB |
Binary file not shown.
Before Width: | Height: | Size: 1.0 MiB |
@ -249,8 +249,7 @@
|
||||
"documentation/platform/scim/overview",
|
||||
"documentation/platform/scim/okta",
|
||||
"documentation/platform/scim/azure",
|
||||
"documentation/platform/scim/jumpcloud",
|
||||
"documentation/platform/scim/group-mappings"
|
||||
"documentation/platform/scim/jumpcloud"
|
||||
]
|
||||
}
|
||||
]
|
||||
|
@ -11,23 +11,22 @@ If you're working with Go Lang, the official [Infisical Go SDK](https://github.c
|
||||
- [Package](https://pkg.go.dev/github.com/infisical/go-sdk)
|
||||
- [Github Repository](https://github.com/infisical/go-sdk)
|
||||
|
||||
# Basic Usage
|
||||
## Basic Usage
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"context"
|
||||
infisical "github.com/infisical/go-sdk"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
infisical "github.com/infisical/go-sdk"
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
||||
client := infisical.NewInfisicalClient(context.Background(), infisical.Config{
|
||||
client := infisical.NewInfisicalClient(infisical.Config{
|
||||
SiteUrl: "https://app.infisical.com", // Optional, default is https://app.infisical.com
|
||||
AutoTokenRefresh: true, // Wether or not to let the SDK handle the access token lifecycle. Defaults to true if not specified.
|
||||
})
|
||||
|
||||
_, err = client.Auth().UniversalAuthLogin("YOUR_CLIENT_ID", "YOUR_CLIENT_SECRET")
|
||||
@ -65,68 +64,32 @@ This example demonstrates how to use the Infisical Go SDK in a simple Go applica
|
||||
```console
|
||||
$ go get github.com/infisical/go-sdk
|
||||
```
|
||||
|
||||
# Configuration
|
||||
|
||||
Import the SDK and create a client instance.
|
||||
|
||||
```go
|
||||
client := infisical.NewInfisicalClient(context.Background(), infisical.Config{
|
||||
client := infisical.NewInfisicalClient(infisical.Config{
|
||||
SiteUrl: "https://app.infisical.com", // Optional, default is https://api.infisical.com
|
||||
})
|
||||
```
|
||||
|
||||
### Configuration Options
|
||||
### ClientSettings methods
|
||||
|
||||
<ParamField query="options" type="object">
|
||||
<Expandable title="properties">
|
||||
<ParamField query="SiteUrl" type="string" optional default="https://app.infisical.com">
|
||||
The URL of the Infisical API..
|
||||
<ParamField query="SiteUrl" type="string" optional>
|
||||
The URL of the Infisical API. Default is `https://api.infisical.com`.
|
||||
</ParamField>
|
||||
|
||||
<ParamField query="UserAgent" type="string">
|
||||
<ParamField query="UserAgent" type="string" required>
|
||||
Optionally set the user agent that will be used for HTTP requests. _(Not recommended)_
|
||||
</ParamField>
|
||||
|
||||
<ParamField query="AutoTokenRefresh" type="boolean" default={true} optional>
|
||||
Whether or not to let the SDK handle the access token lifecycle. Defaults to true if not specified.
|
||||
</ParamField>
|
||||
|
||||
<ParamField query="SilentMode" type="boolean" default={false} optional>
|
||||
Whether or not to suppress logs such as warnings from the token refreshing process. Defaults to false if not specified.
|
||||
</ParamField>
|
||||
</Expandable>
|
||||
|
||||
</ParamField>
|
||||
|
||||
# Automatic token refreshing
|
||||
|
||||
The Infisical Go SDK supports automatic token refreshing. After using one of the auth methods such as Universal Auth, the SDK will automatically renew and re-authenticate when needed.
|
||||
This behavior is enabled by default, but you can opt-out by setting `AutoTokenRefresh` to `false` in the client settings.
|
||||
|
||||
```go
|
||||
client := infisical.NewInfisicalClient(context.Background(), infisical.Config{
|
||||
AutoTokenRefresh: false, // <- Disable automatic token refreshing
|
||||
})
|
||||
```
|
||||
|
||||
When using automatic token refreshing it's important to understand how your application uses the Infiiscal client. If you are instantiating new instances of the client often, it's important to cancel the context when the client is no longer needed to avoid the token refreshing process from running indefinitely.
|
||||
|
||||
```go
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel() // Cancel the context when the client is no longer needed
|
||||
|
||||
client := infisical.NewInfisicalClient(ctx, infisical.Config{
|
||||
AutoTokenRefresh: true,
|
||||
})
|
||||
|
||||
// Use the client
|
||||
```
|
||||
|
||||
This is only necessary if you are creating multiple instances of the client, and those instances are deleted or otherwise removed throughout the application lifecycle.
|
||||
If you are only creating one instance of the client, and it will be used throughout the lifetime of your application, you don't need to worry about this.
|
||||
|
||||
# Authentication
|
||||
### Authentication
|
||||
|
||||
The SDK supports a variety of authentication methods. The most common authentication method is Universal Auth, which uses a client ID and client secret to authenticate.
|
||||
|
||||
@ -259,12 +222,9 @@ if err != nil {
|
||||
}
|
||||
```
|
||||
|
||||
## Working With Secrets
|
||||
## Working with Secrets
|
||||
|
||||
### List Secrets
|
||||
`client.Secrets().List(options)`
|
||||
|
||||
Retrieve all secrets within the Infisical project and environment that client is connected to.
|
||||
### client.Secrets().List(options)
|
||||
|
||||
```go
|
||||
secrets, err := client.Secrets().List(infisical.ListSecretsOptions{
|
||||
@ -275,7 +235,9 @@ secrets, err := client.Secrets().List(infisical.ListSecretsOptions{
|
||||
})
|
||||
```
|
||||
|
||||
### Parameters
|
||||
Retrieve all secrets within the Infisical project and environment that client is connected to
|
||||
|
||||
#### Parameters
|
||||
|
||||
<ParamField query="Parameters" type="object">
|
||||
<Expandable title="properties">
|
||||
@ -310,11 +272,7 @@ secrets, err := client.Secrets().List(infisical.ListSecretsOptions{
|
||||
|
||||
</ParamField>
|
||||
|
||||
###
|
||||
### Retrieve Secret
|
||||
`client.Secrets().Retrieve(options)`
|
||||
|
||||
Retrieve a secret from Infisical. By default `Secrets().Retrieve()` fetches and returns a shared secret.
|
||||
### client.Secrets().Retrieve(options)
|
||||
|
||||
```go
|
||||
secret, err := client.Secrets().Retrieve(infisical.RetrieveSecretOptions{
|
||||
@ -324,7 +282,11 @@ secret, err := client.Secrets().Retrieve(infisical.RetrieveSecretOptions{
|
||||
})
|
||||
```
|
||||
|
||||
### Parameters
|
||||
Retrieve a secret from Infisical.
|
||||
|
||||
By default, `Secrets().Retrieve()` fetches and returns a shared secret.
|
||||
|
||||
#### Parameters
|
||||
|
||||
<ParamField query="Parameters" type="object" optional>
|
||||
<Expandable title="properties">
|
||||
@ -346,11 +308,7 @@ secret, err := client.Secrets().Retrieve(infisical.RetrieveSecretOptions{
|
||||
</Expandable>
|
||||
</ParamField>
|
||||
|
||||
###
|
||||
### Create Secret
|
||||
`client.Secrets().Create(options)`
|
||||
|
||||
Create a new secret in Infisical.
|
||||
### client.Secrets().Create(options)
|
||||
|
||||
```go
|
||||
secret, err := client.Secrets().Create(infisical.CreateSecretOptions{
|
||||
@ -363,8 +321,9 @@ secret, err := client.Secrets().Create(infisical.CreateSecretOptions{
|
||||
})
|
||||
```
|
||||
|
||||
Create a new secret in Infisical.
|
||||
|
||||
### Parameters
|
||||
#### Parameters
|
||||
|
||||
<ParamField query="Parameters" type="object" optional>
|
||||
<Expandable title="properties">
|
||||
@ -392,12 +351,7 @@ secret, err := client.Secrets().Create(infisical.CreateSecretOptions{
|
||||
</Expandable>
|
||||
</ParamField>
|
||||
|
||||
###
|
||||
### Update Secret
|
||||
|
||||
`client.Secrets().Update(options)`
|
||||
|
||||
Update an existing secret in Infisical.
|
||||
### client.Secrets().Update(options)
|
||||
|
||||
```go
|
||||
secret, err := client.Secrets().Update(infisical.UpdateSecretOptions{
|
||||
@ -409,7 +363,9 @@ secret, err := client.Secrets().Update(infisical.UpdateSecretOptions{
|
||||
})
|
||||
```
|
||||
|
||||
### Parameters
|
||||
Update an existing secret in Infisical.
|
||||
|
||||
#### Parameters
|
||||
|
||||
<ParamField query="Parameters" type="object" optional>
|
||||
<Expandable title="properties">
|
||||
@ -437,11 +393,7 @@ secret, err := client.Secrets().Update(infisical.UpdateSecretOptions{
|
||||
</Expandable>
|
||||
</ParamField>
|
||||
|
||||
###
|
||||
### Delete Secret
|
||||
`client.Secrets().Delete(options)`
|
||||
|
||||
Delete a secret in Infisical.
|
||||
### client.Secrets().Delete(options)
|
||||
|
||||
```go
|
||||
secret, err := client.Secrets().Delete(infisical.DeleteSecretOptions{
|
||||
@ -451,7 +403,9 @@ secret, err := client.Secrets().Delete(infisical.DeleteSecretOptions{
|
||||
})
|
||||
```
|
||||
|
||||
### Parameters
|
||||
Delete a secret in Infisical.
|
||||
|
||||
#### Parameters
|
||||
|
||||
<ParamField query="Parameters" type="object" optional>
|
||||
<Expandable title="properties">
|
||||
@ -473,14 +427,10 @@ secret, err := client.Secrets().Delete(infisical.DeleteSecretOptions{
|
||||
</Expandable>
|
||||
</ParamField>
|
||||
|
||||
## Working With folders
|
||||
## Working with folders
|
||||
|
||||
|
||||
###
|
||||
### List Folders
|
||||
`client.Folders().List(options)`
|
||||
|
||||
Retrieve all within the Infisical project and environment that client is connected to.
|
||||
### client.Folders().List(options)
|
||||
|
||||
```go
|
||||
folders, err := client.Folders().List(infisical.ListFoldersOptions{
|
||||
@ -490,7 +440,9 @@ folders, err := client.Folders().List(infisical.ListFoldersOptions{
|
||||
})
|
||||
```
|
||||
|
||||
### Parameters
|
||||
Retrieve all within the Infisical project and environment that client is connected to.
|
||||
|
||||
#### Parameters
|
||||
|
||||
<ParamField query="Parameters" type="object">
|
||||
<Expandable title="properties">
|
||||
@ -509,11 +461,7 @@ folders, err := client.Folders().List(infisical.ListFoldersOptions{
|
||||
|
||||
</ParamField>
|
||||
|
||||
###
|
||||
### Create Folder
|
||||
`client.Folders().Create(options)`
|
||||
|
||||
Create a new folder in Infisical.
|
||||
### client.Folders().Create(options)
|
||||
|
||||
```go
|
||||
folder, err := client.Folders().Create(infisical.CreateFolderOptions{
|
||||
@ -524,7 +472,9 @@ folder, err := client.Folders().Create(infisical.CreateFolderOptions{
|
||||
})
|
||||
```
|
||||
|
||||
### Parameters
|
||||
Create a new folder in Infisical.
|
||||
|
||||
#### Parameters
|
||||
|
||||
<ParamField query="Parameters" type="object" optional>
|
||||
<Expandable title="properties">
|
||||
@ -544,11 +494,8 @@ folder, err := client.Folders().Create(infisical.CreateFolderOptions{
|
||||
</ParamField>
|
||||
|
||||
|
||||
###
|
||||
### Update Folder
|
||||
`client.Folders().Update(options)`
|
||||
|
||||
Update an existing folder in Infisical.
|
||||
### client.Folders().Update(options)
|
||||
|
||||
```go
|
||||
folder, err := client.Folders().Update(infisical.UpdateFolderOptions{
|
||||
@ -560,7 +507,9 @@ folder, err := client.Folders().Update(infisical.UpdateFolderOptions{
|
||||
})
|
||||
```
|
||||
|
||||
### Parameters
|
||||
Update an existing folder in Infisical.
|
||||
|
||||
#### Parameters
|
||||
|
||||
<ParamField query="Parameters" type="object" optional>
|
||||
<Expandable title="properties">
|
||||
@ -582,11 +531,7 @@ folder, err := client.Folders().Update(infisical.UpdateFolderOptions{
|
||||
</Expandable>
|
||||
</ParamField>
|
||||
|
||||
###
|
||||
### Delete Folder
|
||||
`client.Folders().Delete(options)`
|
||||
|
||||
Delete a folder in Infisical.
|
||||
### client.Folders().Delete(options)
|
||||
|
||||
```go
|
||||
deletedFolder, err := client.Folders().Delete(infisical.DeleteFolderOptions{
|
||||
@ -599,7 +544,9 @@ deletedFolder, err := client.Folders().Delete(infisical.DeleteFolderOptions{
|
||||
})
|
||||
```
|
||||
|
||||
### Parameters
|
||||
Delete a folder in Infisical.
|
||||
|
||||
#### Parameters
|
||||
|
||||
<ParamField query="Parameters" type="object" optional>
|
||||
<Expandable title="properties">
|
||||
@ -620,6 +567,4 @@ deletedFolder, err := client.Folders().Delete(infisical.DeleteFolderOptions{
|
||||
The path from where the folder should be deleted.
|
||||
</ParamField>
|
||||
</Expandable>
|
||||
</ParamField>
|
||||
|
||||
|
||||
</ParamField>
|
@ -115,10 +115,7 @@ export const CreateTagModal = ({ isOpen, onToggle }: Props): JSX.Element => {
|
||||
formState: { isSubmitting },
|
||||
handleSubmit
|
||||
} = useForm<FormData>({
|
||||
resolver: zodResolver(createTagSchema),
|
||||
defaultValues: {
|
||||
color: secretTagsColors[0].hex
|
||||
}
|
||||
resolver: zodResolver(createTagSchema)
|
||||
});
|
||||
|
||||
const { currentWorkspace } = useWorkspace();
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { ReactNode } from "react";
|
||||
import { faCheck, faMinus } from "@fortawesome/free-solid-svg-icons";
|
||||
import { faCheck } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import * as CheckboxPrimitive from "@radix-ui/react-checkbox";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
@ -15,7 +15,6 @@ export type CheckboxProps = Omit<
|
||||
isRequired?: boolean;
|
||||
checkIndicatorBg?: string | undefined;
|
||||
isError?: boolean;
|
||||
isIndeterminate?: boolean;
|
||||
};
|
||||
|
||||
export const Checkbox = ({
|
||||
@ -27,7 +26,6 @@ export const Checkbox = ({
|
||||
isRequired,
|
||||
checkIndicatorBg,
|
||||
isError,
|
||||
isIndeterminate,
|
||||
...props
|
||||
}: CheckboxProps): JSX.Element => {
|
||||
return (
|
||||
@ -47,11 +45,7 @@ export const Checkbox = ({
|
||||
id={id}
|
||||
>
|
||||
<CheckboxPrimitive.Indicator className={`${checkIndicatorBg || "text-bunker-800"}`}>
|
||||
{isIndeterminate ? (
|
||||
<FontAwesomeIcon icon={faMinus} size="sm" />
|
||||
) : (
|
||||
<FontAwesomeIcon icon={faCheck} size="sm" />
|
||||
)}
|
||||
<FontAwesomeIcon icon={faCheck} size="sm" />
|
||||
</CheckboxPrimitive.Indicator>
|
||||
</CheckboxPrimitive.Root>
|
||||
<label
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { DetailedHTMLProps, HTMLAttributes, ReactNode, TdHTMLAttributes } from "react";
|
||||
import { HTMLAttributes, ReactNode, TdHTMLAttributes } from "react";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
|
||||
import { Skeleton } from "../Skeleton";
|
||||
@ -7,13 +7,12 @@ export type TableContainerProps = {
|
||||
children: ReactNode;
|
||||
isRounded?: boolean;
|
||||
className?: string;
|
||||
} & DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>;
|
||||
};
|
||||
|
||||
export const TableContainer = ({
|
||||
children,
|
||||
className,
|
||||
isRounded = true,
|
||||
...props
|
||||
isRounded = true
|
||||
}: TableContainerProps): JSX.Element => (
|
||||
<div
|
||||
className={twMerge(
|
||||
@ -21,7 +20,6 @@ export const TableContainer = ({
|
||||
isRounded && "rounded-lg",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
|
@ -230,7 +230,6 @@ export const AppLayout = ({ children }: LayoutProps) => {
|
||||
(!orgs?.map((org) => org.id)?.includes(router.query.id as string) &&
|
||||
!router.asPath.includes("project") &&
|
||||
!router.asPath.includes("personal") &&
|
||||
!router.asPath.includes("secret-scanning") &&
|
||||
!router.asPath.includes("integration")))
|
||||
) {
|
||||
router.push(`/org/${currentOrg?.id}/overview`);
|
||||
|
@ -72,8 +72,7 @@ const SecretScanning = withPermission(
|
||||
</div>
|
||||
{config.isSecretScanningDisabled && (
|
||||
<NoticeBanner title="Secret scanning is in maintenance" className="mb-4">
|
||||
We are working on improving the performance of secret scanning due to increased
|
||||
usage.
|
||||
We are working on improving the performance of secret scanning due to increased usage.
|
||||
</NoticeBanner>
|
||||
)}
|
||||
<div className="relative mb-6 flex justify-between rounded-md border border-mineshaft-600 bg-mineshaft-800 p-6">
|
||||
@ -117,7 +116,7 @@ const SecretScanning = withPermission(
|
||||
colorSchema="primary"
|
||||
onClick={generateNewIntegrationSession}
|
||||
className="h-min py-2"
|
||||
isDisabled={!isAllowed}
|
||||
isDisabled={!isAllowed || config.isSecretScanningDisabled}
|
||||
>
|
||||
Integrate with GitHub
|
||||
</Button>
|
||||
|
@ -270,7 +270,7 @@ export const IdentityKubernetesAuthForm = ({
|
||||
label="Allowed Namespaces"
|
||||
isError={Boolean(error)}
|
||||
errorText={error?.message}
|
||||
tooltipText="A comma-separated list of trusted namespaces that service accounts must belong to authenticate with Infisical."
|
||||
tooltipText="An optional comma-separated list of trusted service account names that are allowed to authenticate with Infisical. Leave empty to allow any namespaces."
|
||||
>
|
||||
<Input {...field} placeholder="namespaceA, namespaceB" type="text" />
|
||||
</FormControl>
|
||||
|
@ -2,33 +2,29 @@ import { createContext, ReactNode, useContext, useEffect, useRef } from "react";
|
||||
import { useRouter } from "next/router";
|
||||
import { createStore, StateCreator, StoreApi, useStore } from "zustand";
|
||||
|
||||
import { SecretV3RawSanitized } from "@app/hooks/api/secrets/types";
|
||||
|
||||
// akhilmhdh: Don't remove this file if ur thinking why use zustand just for selected selects state
|
||||
// This is first step and the whole secret crud will be moved to this global page scope state
|
||||
// this will allow more stuff like undo grouping stuffs etc
|
||||
type SelectedSecretState = {
|
||||
selectedSecret: Record<string, SecretV3RawSanitized>;
|
||||
selectedSecret: Record<string, boolean>;
|
||||
action: {
|
||||
toggle: (secret: SecretV3RawSanitized) => void;
|
||||
toggle: (id: string) => void;
|
||||
reset: () => void;
|
||||
set: (secrets: Record<string, SecretV3RawSanitized>) => void;
|
||||
};
|
||||
};
|
||||
const createSelectedSecretStore: StateCreator<SelectedSecretState> = (set) => ({
|
||||
selectedSecret: {},
|
||||
action: {
|
||||
toggle: (secret) =>
|
||||
toggle: (id) =>
|
||||
set((state) => {
|
||||
const isChecked = Boolean(state.selectedSecret?.[secret.id]);
|
||||
const isChecked = Boolean(state.selectedSecret?.[id]);
|
||||
const newChecks = { ...state.selectedSecret };
|
||||
// remove selection if its present else add it
|
||||
if (isChecked) delete newChecks[secret.id];
|
||||
else newChecks[secret.id] = secret;
|
||||
if (isChecked) delete newChecks[id];
|
||||
else newChecks[id] = true;
|
||||
return { selectedSecret: newChecks };
|
||||
}),
|
||||
reset: () => set({ selectedSecret: {} }),
|
||||
set: (secrets) => set({ selectedSecret: secrets })
|
||||
reset: () => set({ selectedSecret: {} })
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useRouter } from "next/router";
|
||||
import { subject } from "@casl/ability";
|
||||
@ -9,7 +9,7 @@ import { twMerge } from "tailwind-merge";
|
||||
import NavHeader from "@app/components/navigation/NavHeader";
|
||||
import { createNotification } from "@app/components/notifications";
|
||||
import { PermissionDeniedBanner } from "@app/components/permissions";
|
||||
import { Checkbox, ContentLoader, Pagination, Tooltip } from "@app/components/v2";
|
||||
import { ContentLoader, Pagination } from "@app/components/v2";
|
||||
import {
|
||||
ProjectPermissionActions,
|
||||
ProjectPermissionSub,
|
||||
@ -39,11 +39,7 @@ import { PitDrawer } from "./components/PitDrawer";
|
||||
import { SecretDropzone } from "./components/SecretDropzone";
|
||||
import { SecretListView } from "./components/SecretListView";
|
||||
import { SnapshotView } from "./components/SnapshotView";
|
||||
import {
|
||||
StoreProvider,
|
||||
useSelectedSecretActions,
|
||||
useSelectedSecrets
|
||||
} from "./SecretMainPage.store";
|
||||
import { StoreProvider } from "./SecretMainPage.store";
|
||||
import { Filter, RowType } from "./SecretMainPage.types";
|
||||
|
||||
const LOADER_TEXT = [
|
||||
@ -52,7 +48,7 @@ const LOADER_TEXT = [
|
||||
"Getting secret import links..."
|
||||
];
|
||||
|
||||
const SecretMainPageContent = () => {
|
||||
export const SecretMainPage = () => {
|
||||
const { t } = useTranslation();
|
||||
const { currentWorkspace, isLoading: isWorkspaceLoading } = useWorkspace();
|
||||
const router = useRouter();
|
||||
@ -287,33 +283,6 @@ const SecretMainPageContent = () => {
|
||||
}
|
||||
}, [secretPath]);
|
||||
|
||||
const selectedSecrets = useSelectedSecrets();
|
||||
const selectedSecretActions = useSelectedSecretActions();
|
||||
|
||||
const allRowsSelectedOnPage = useMemo(() => {
|
||||
if (secrets?.every((secret) => selectedSecrets[secret.id]))
|
||||
return { isChecked: true, isIndeterminate: false };
|
||||
|
||||
if (secrets?.some((secret) => selectedSecrets[secret.id]))
|
||||
return { isChecked: true, isIndeterminate: true };
|
||||
|
||||
return { isChecked: false, isIndeterminate: false };
|
||||
}, [selectedSecrets, secrets]);
|
||||
|
||||
const toggleSelectAllRows = () => {
|
||||
const newChecks = { ...selectedSecrets };
|
||||
|
||||
secrets?.forEach((secret) => {
|
||||
if (allRowsSelectedOnPage.isChecked) {
|
||||
delete newChecks[secret.id];
|
||||
} else {
|
||||
newChecks[secret.id] = secret;
|
||||
}
|
||||
});
|
||||
|
||||
selectedSecretActions.set(newChecks);
|
||||
};
|
||||
|
||||
if (isDetailsLoading) {
|
||||
return <ContentLoader text={LOADER_TEXT} />;
|
||||
}
|
||||
@ -331,192 +300,169 @@ const SecretMainPageContent = () => {
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="container mx-auto flex flex-col px-6 text-mineshaft-50 dark:[color-scheme:dark]">
|
||||
<SecretV2MigrationSection />
|
||||
<div className="relative right-6 -top-2 mb-2 ml-6">
|
||||
<NavHeader
|
||||
pageName={t("dashboard.title")}
|
||||
currentEnv={environment}
|
||||
userAvailableEnvs={currentWorkspace?.environments}
|
||||
isFolderMode
|
||||
secretPath={secretPath}
|
||||
isProjectRelated
|
||||
onEnvChange={handleEnvChange}
|
||||
isProtectedBranch={isProtectedBranch}
|
||||
protectionPolicyName={boardPolicy?.name}
|
||||
/>
|
||||
</div>
|
||||
{!isRollbackMode ? (
|
||||
<>
|
||||
<ActionBar
|
||||
environment={environment}
|
||||
workspaceId={workspaceId}
|
||||
projectSlug={projectSlug}
|
||||
<StoreProvider>
|
||||
<div className="container mx-auto flex flex-col px-6 text-mineshaft-50 dark:[color-scheme:dark]">
|
||||
<SecretV2MigrationSection />
|
||||
<div className="relative right-6 -top-2 mb-2 ml-6">
|
||||
<NavHeader
|
||||
pageName={t("dashboard.title")}
|
||||
currentEnv={environment}
|
||||
userAvailableEnvs={currentWorkspace?.environments}
|
||||
isFolderMode
|
||||
secretPath={secretPath}
|
||||
isVisible={isVisible}
|
||||
filter={filter}
|
||||
tags={tags}
|
||||
onVisibilityToggle={handleToggleVisibility}
|
||||
onSearchChange={handleSearchChange}
|
||||
onToggleTagFilter={handleTagToggle}
|
||||
snapshotCount={snapshotCount || 0}
|
||||
isSnapshotCountLoading={isSnapshotCountLoading}
|
||||
onToggleRowType={handleToggleRowType}
|
||||
onClickRollbackMode={() => handlePopUpToggle("snapshots", true)}
|
||||
isProjectRelated
|
||||
onEnvChange={handleEnvChange}
|
||||
isProtectedBranch={isProtectedBranch}
|
||||
protectionPolicyName={boardPolicy?.name}
|
||||
/>
|
||||
<div className="thin-scrollbar mt-3 overflow-y-auto overflow-x-hidden rounded-md rounded-b-none bg-mineshaft-800 text-left text-sm text-bunker-300">
|
||||
<div className="flex flex-col" id="dashboard">
|
||||
{isNotEmpty && (
|
||||
<div
|
||||
className={twMerge(
|
||||
"sticky top-0 flex border-b border-mineshaft-600 bg-mineshaft-800 font-medium"
|
||||
)}
|
||||
>
|
||||
<div
|
||||
className="flex w-80 flex-shrink-0 items-center border-r border-mineshaft-600 px-4 py-2"
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
onClick={handleSortToggle}
|
||||
onKeyDown={(evt) => {
|
||||
if (evt.key === "Enter") handleSortToggle();
|
||||
}}
|
||||
>
|
||||
<Tooltip
|
||||
className="max-w-[20rem] whitespace-nowrap"
|
||||
content={
|
||||
totalCount > 0
|
||||
? `${
|
||||
!allRowsSelectedOnPage.isChecked ? "Select" : "Unselect"
|
||||
} all secrets on page`
|
||||
: ""
|
||||
}
|
||||
>
|
||||
<div className="mr-6 ml-1">
|
||||
<Checkbox
|
||||
isDisabled={totalCount === 0}
|
||||
id="checkbox-select-all-rows"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
isChecked={allRowsSelectedOnPage.isChecked}
|
||||
isIndeterminate={allRowsSelectedOnPage.isIndeterminate}
|
||||
onCheckedChange={toggleSelectAllRows}
|
||||
/>
|
||||
</div>
|
||||
</Tooltip>
|
||||
Key
|
||||
<FontAwesomeIcon
|
||||
icon={orderDirection === OrderByDirection.ASC ? faArrowDown : faArrowUp}
|
||||
className="ml-2"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex-grow px-4 py-2">Value</div>
|
||||
</div>
|
||||
)}
|
||||
{canReadSecret && imports?.length && (
|
||||
<SecretImportListView
|
||||
searchTerm={debouncedSearchFilter}
|
||||
secretImports={imports}
|
||||
isFetching={isDetailsFetching}
|
||||
environment={environment}
|
||||
workspaceId={workspaceId}
|
||||
secretPath={secretPath}
|
||||
importedSecrets={importedSecrets}
|
||||
/>
|
||||
)}
|
||||
{folders?.length && (
|
||||
<FolderListView
|
||||
folders={folders}
|
||||
environment={environment}
|
||||
workspaceId={workspaceId}
|
||||
secretPath={secretPath}
|
||||
onNavigateToFolder={handleResetFilter}
|
||||
/>
|
||||
)}
|
||||
{canReadSecret && dynamicSecrets?.length && (
|
||||
<DynamicSecretListView
|
||||
environment={environment}
|
||||
projectSlug={projectSlug}
|
||||
secretPath={secretPath}
|
||||
dynamicSecrets={dynamicSecrets}
|
||||
/>
|
||||
)}
|
||||
{canReadSecret && secrets?.length && (
|
||||
<SecretListView
|
||||
secrets={secrets}
|
||||
tags={tags}
|
||||
isVisible={isVisible}
|
||||
environment={environment}
|
||||
workspaceId={workspaceId}
|
||||
secretPath={secretPath}
|
||||
isProtectedBranch={isProtectedBranch}
|
||||
/>
|
||||
)}
|
||||
{!canReadSecret && folders?.length === 0 && <PermissionDeniedBanner />}
|
||||
</div>
|
||||
</div>
|
||||
{!isDetailsLoading && totalCount > 0 && (
|
||||
<Pagination
|
||||
startAdornment={
|
||||
<SecretTableResourceCount
|
||||
dynamicSecretCount={totalDynamicSecretCount}
|
||||
importCount={totalImportCount}
|
||||
secretCount={totalSecretCount}
|
||||
folderCount={totalFolderCount}
|
||||
/>
|
||||
}
|
||||
className="rounded-b-md border-t border-solid border-t-mineshaft-600"
|
||||
count={totalCount}
|
||||
page={page}
|
||||
perPage={perPage}
|
||||
onChangePage={(newPage) => setPage(newPage)}
|
||||
onChangePerPage={(newPerPage) => setPerPage(newPerPage)}
|
||||
</div>
|
||||
{!isRollbackMode ? (
|
||||
<>
|
||||
<ActionBar
|
||||
secrets={secrets}
|
||||
environment={environment}
|
||||
workspaceId={workspaceId}
|
||||
projectSlug={projectSlug}
|
||||
secretPath={secretPath}
|
||||
isVisible={isVisible}
|
||||
filter={filter}
|
||||
tags={tags}
|
||||
onVisibilityToggle={handleToggleVisibility}
|
||||
onSearchChange={handleSearchChange}
|
||||
onToggleTagFilter={handleTagToggle}
|
||||
snapshotCount={snapshotCount || 0}
|
||||
isSnapshotCountLoading={isSnapshotCountLoading}
|
||||
onToggleRowType={handleToggleRowType}
|
||||
onClickRollbackMode={() => handlePopUpToggle("snapshots", true)}
|
||||
/>
|
||||
)}
|
||||
<CreateSecretForm
|
||||
<div className="thin-scrollbar mt-3 overflow-y-auto overflow-x-hidden rounded-md rounded-b-none bg-mineshaft-800 text-left text-sm text-bunker-300">
|
||||
<div className="flex flex-col" id="dashboard">
|
||||
{isNotEmpty && (
|
||||
<div
|
||||
className={twMerge(
|
||||
"sticky top-0 flex border-b border-mineshaft-600 bg-mineshaft-800 font-medium"
|
||||
)}
|
||||
>
|
||||
<div style={{ width: "2.8rem" }} className="flex-shrink-0 px-4 py-3" />
|
||||
<div
|
||||
className="flex w-80 flex-shrink-0 items-center border-r border-mineshaft-600 px-4 py-2"
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
onClick={handleSortToggle}
|
||||
onKeyDown={(evt) => {
|
||||
if (evt.key === "Enter") handleSortToggle();
|
||||
}}
|
||||
>
|
||||
Key
|
||||
<FontAwesomeIcon
|
||||
icon={orderDirection === OrderByDirection.ASC ? faArrowDown : faArrowUp}
|
||||
className="ml-2"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex-grow px-4 py-2">Value</div>
|
||||
</div>
|
||||
)}
|
||||
{canReadSecret && imports?.length && (
|
||||
<SecretImportListView
|
||||
searchTerm={debouncedSearchFilter}
|
||||
secretImports={imports}
|
||||
isFetching={isDetailsFetching}
|
||||
environment={environment}
|
||||
workspaceId={workspaceId}
|
||||
secretPath={secretPath}
|
||||
importedSecrets={importedSecrets}
|
||||
/>
|
||||
)}
|
||||
{folders?.length && (
|
||||
<FolderListView
|
||||
folders={folders}
|
||||
environment={environment}
|
||||
workspaceId={workspaceId}
|
||||
secretPath={secretPath}
|
||||
onNavigateToFolder={handleResetFilter}
|
||||
/>
|
||||
)}
|
||||
{canReadSecret && dynamicSecrets?.length && (
|
||||
<DynamicSecretListView
|
||||
environment={environment}
|
||||
projectSlug={projectSlug}
|
||||
secretPath={secretPath}
|
||||
dynamicSecrets={dynamicSecrets}
|
||||
/>
|
||||
)}
|
||||
{canReadSecret && secrets?.length && (
|
||||
<SecretListView
|
||||
secrets={secrets}
|
||||
tags={tags}
|
||||
isVisible={isVisible}
|
||||
environment={environment}
|
||||
workspaceId={workspaceId}
|
||||
secretPath={secretPath}
|
||||
isProtectedBranch={isProtectedBranch}
|
||||
/>
|
||||
)}
|
||||
{!canReadSecret && folders?.length === 0 && <PermissionDeniedBanner />}
|
||||
</div>
|
||||
</div>
|
||||
{!isDetailsLoading && totalCount > 0 && (
|
||||
<Pagination
|
||||
startAdornment={
|
||||
<SecretTableResourceCount
|
||||
dynamicSecretCount={totalDynamicSecretCount}
|
||||
importCount={totalImportCount}
|
||||
secretCount={totalSecretCount}
|
||||
folderCount={totalFolderCount}
|
||||
/>
|
||||
}
|
||||
className="rounded-b-md border-t border-solid border-t-mineshaft-600"
|
||||
count={totalCount}
|
||||
page={page}
|
||||
perPage={perPage}
|
||||
onChangePage={(newPage) => setPage(newPage)}
|
||||
onChangePerPage={(newPerPage) => setPerPage(newPerPage)}
|
||||
/>
|
||||
)}
|
||||
<CreateSecretForm
|
||||
environment={environment}
|
||||
workspaceId={workspaceId}
|
||||
secretPath={secretPath}
|
||||
autoCapitalize={currentWorkspace?.autoCapitalization}
|
||||
isProtectedBranch={isProtectedBranch}
|
||||
/>
|
||||
<SecretDropzone
|
||||
secrets={secrets}
|
||||
environment={environment}
|
||||
workspaceId={workspaceId}
|
||||
secretPath={secretPath}
|
||||
isSmaller={isNotEmpty}
|
||||
environments={currentWorkspace?.environments}
|
||||
isProtectedBranch={isProtectedBranch}
|
||||
/>
|
||||
<PitDrawer
|
||||
secretSnaphots={snapshotList}
|
||||
snapshotId={snapshotId}
|
||||
isDrawerOpen={popUp.snapshots.isOpen}
|
||||
onOpenChange={(isOpen) => handlePopUpToggle("snapshots", isOpen)}
|
||||
hasNextPage={hasNextSnapshotListPage}
|
||||
fetchNextPage={fetchNextSnapshotList}
|
||||
onSelectSnapshot={handleSelectSnapshot}
|
||||
isFetchingNextPage={isFetchingNextSnapshotList}
|
||||
/>
|
||||
</>
|
||||
) : (
|
||||
<SnapshotView
|
||||
snapshotId={snapshotId || ""}
|
||||
environment={environment}
|
||||
workspaceId={workspaceId}
|
||||
secretPath={secretPath}
|
||||
autoCapitalize={currentWorkspace?.autoCapitalization}
|
||||
isProtectedBranch={isProtectedBranch}
|
||||
/>
|
||||
<SecretDropzone
|
||||
secrets={secrets}
|
||||
environment={environment}
|
||||
workspaceId={workspaceId}
|
||||
secretPath={secretPath}
|
||||
isSmaller={isNotEmpty}
|
||||
environments={currentWorkspace?.environments}
|
||||
isProtectedBranch={isProtectedBranch}
|
||||
folders={folders}
|
||||
snapshotCount={snapshotCount}
|
||||
onGoBack={handleResetSnapshot}
|
||||
onClickListSnapshot={() => handlePopUpToggle("snapshots", true)}
|
||||
/>
|
||||
<PitDrawer
|
||||
secretSnaphots={snapshotList}
|
||||
snapshotId={snapshotId}
|
||||
isDrawerOpen={popUp.snapshots.isOpen}
|
||||
onOpenChange={(isOpen) => handlePopUpToggle("snapshots", isOpen)}
|
||||
hasNextPage={hasNextSnapshotListPage}
|
||||
fetchNextPage={fetchNextSnapshotList}
|
||||
onSelectSnapshot={handleSelectSnapshot}
|
||||
isFetchingNextPage={isFetchingNextSnapshotList}
|
||||
/>
|
||||
</>
|
||||
) : (
|
||||
<SnapshotView
|
||||
snapshotId={snapshotId || ""}
|
||||
environment={environment}
|
||||
workspaceId={workspaceId}
|
||||
secretPath={secretPath}
|
||||
secrets={secrets}
|
||||
folders={folders}
|
||||
snapshotCount={snapshotCount}
|
||||
onGoBack={handleResetSnapshot}
|
||||
onClickListSnapshot={() => handlePopUpToggle("snapshots", true)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</StoreProvider>
|
||||
);
|
||||
};
|
||||
|
||||
export const SecretMainPage = () => (
|
||||
<StoreProvider>
|
||||
<SecretMainPageContent />
|
||||
</StoreProvider>
|
||||
);
|
||||
|
@ -54,7 +54,7 @@ import {
|
||||
import { usePopUp } from "@app/hooks";
|
||||
import { useCreateFolder, useDeleteSecretBatch, useMoveSecrets } from "@app/hooks/api";
|
||||
import { fetchProjectSecrets } from "@app/hooks/api/secrets/queries";
|
||||
import { SecretType, WsTag } from "@app/hooks/api/types";
|
||||
import { SecretType, SecretV3RawSanitized, WsTag } from "@app/hooks/api/types";
|
||||
|
||||
import {
|
||||
PopUpNames,
|
||||
@ -69,7 +69,8 @@ import { FolderForm } from "./FolderForm";
|
||||
import { MoveSecretsModal } from "./MoveSecretsModal";
|
||||
|
||||
type Props = {
|
||||
// switch the secrets type as it gets decrypted after api call
|
||||
secrets?: SecretV3RawSanitized[];
|
||||
// swtich the secrets type as it gets decrypted after api call
|
||||
environment: string;
|
||||
// @depreciated will be moving all these details to zustand
|
||||
workspaceId: string;
|
||||
@ -88,6 +89,7 @@ type Props = {
|
||||
};
|
||||
|
||||
export const ActionBar = ({
|
||||
secrets = [],
|
||||
environment,
|
||||
workspaceId,
|
||||
projectSlug,
|
||||
@ -200,7 +202,7 @@ export const ActionBar = ({
|
||||
};
|
||||
|
||||
const handleSecretBulkDelete = async () => {
|
||||
const bulkDeletedSecrets = Object.values(selectedSecrets);
|
||||
const bulkDeletedSecrets = secrets.filter(({ id }) => Boolean(selectedSecrets?.[id]));
|
||||
try {
|
||||
await deleteBatchSecretV3({
|
||||
secretPath,
|
||||
@ -233,7 +235,7 @@ export const ActionBar = ({
|
||||
shouldOverwrite: boolean;
|
||||
}) => {
|
||||
try {
|
||||
const secretsToMove = Object.values(selectedSecrets);
|
||||
const secretsToMove = secrets.filter(({ id }) => Boolean(selectedSecrets?.[id]));
|
||||
const { isDestinationUpdated, isSourceUpdated } = await moveSecrets({
|
||||
projectSlug,
|
||||
shouldOverwrite,
|
||||
@ -551,7 +553,7 @@ export const ActionBar = ({
|
||||
<FontAwesomeIcon icon={faMinusSquare} size="lg" />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
<div className="ml-2 flex-grow px-2 text-sm">
|
||||
<div className="ml-4 flex-grow px-2 text-sm">
|
||||
{Object.keys(selectedSecrets).length} Selected
|
||||
</div>
|
||||
<ProjectPermissionCan
|
||||
|
@ -158,17 +158,17 @@ export const SecretImportItem = ({
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div className="flex w-11 items-center py-2 pl-5 text-green-700">
|
||||
<div className="flex w-12 items-center px-4 py-2 text-green-700">
|
||||
<FontAwesomeIcon icon={faFileImport} />
|
||||
</div>
|
||||
<div className="flex flex-grow items-center py-2 pl-4 pr-2">
|
||||
<div className="flex flex-grow items-center px-4 py-2">
|
||||
<EnvFolderIcon
|
||||
env={importEnv.slug || ""}
|
||||
secretPath={secretImport?.importPath || ""}
|
||||
// isReplication={isReplication}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex items-center space-x-4 py-2 pr-4">
|
||||
<div className="flex items-center space-x-4 px-4 py-2">
|
||||
{lastReplicated && (
|
||||
<Tooltip
|
||||
position="left"
|
||||
|
@ -56,7 +56,7 @@ type Props = {
|
||||
onDetailViewSecret: (sec: SecretV3RawSanitized) => void;
|
||||
isVisible?: boolean;
|
||||
isSelected?: boolean;
|
||||
onToggleSecretSelect: (secret: SecretV3RawSanitized) => void;
|
||||
onToggleSecretSelect: (id: string) => void;
|
||||
tags: WsTag[];
|
||||
onCreateTag: () => void;
|
||||
environment: string;
|
||||
@ -218,7 +218,7 @@ export const SecretItem = memo(
|
||||
<Checkbox
|
||||
id={`checkbox-${secret.id}`}
|
||||
isChecked={isSelected}
|
||||
onCheckedChange={() => onToggleSecretSelect(secret)}
|
||||
onCheckedChange={() => onToggleSecretSelect(secret.id)}
|
||||
className={twMerge("ml-3 hidden group-hover:flex", isSelected && "flex")}
|
||||
/>
|
||||
<FontAwesomeSymbol
|
||||
|
@ -304,7 +304,7 @@ export const SecretListView = ({
|
||||
environment={environment}
|
||||
secretPath={secretPath}
|
||||
tags={wsTags}
|
||||
isSelected={Boolean(selectedSecrets?.[secret.id])}
|
||||
isSelected={selectedSecrets?.[secret.id]}
|
||||
onToggleSecretSelect={toggleSelectedSecret}
|
||||
isVisible={isVisible}
|
||||
secret={secret}
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||
import { useCallback, useEffect, useRef, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import Link from "next/link";
|
||||
import { useRouter } from "next/router";
|
||||
@ -25,7 +25,6 @@ import { createNotification } from "@app/components/notifications";
|
||||
import { ProjectPermissionCan } from "@app/components/permissions";
|
||||
import {
|
||||
Button,
|
||||
Checkbox,
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
@ -68,7 +67,7 @@ import { DashboardSecretsOrderBy } from "@app/hooks/api/dashboard/types";
|
||||
import { OrderByDirection } from "@app/hooks/api/generic/types";
|
||||
import { useUpdateFolderBatch } from "@app/hooks/api/secretFolders/queries";
|
||||
import { TUpdateFolderBatchDTO } from "@app/hooks/api/secretFolders/types";
|
||||
import { SecretType, SecretV3RawSanitized, TSecretFolder } from "@app/hooks/api/types";
|
||||
import { SecretType, TSecretFolder } from "@app/hooks/api/types";
|
||||
import { ProjectVersion } from "@app/hooks/api/workspace/types";
|
||||
import { useDynamicSecretOverview, useFolderOverview, useSecretOverview } from "@app/hooks/utils";
|
||||
import { SecretOverviewDynamicSecretRow } from "@app/views/SecretOverviewPage/components/SecretOverviewDynamicSecretRow";
|
||||
@ -107,10 +106,18 @@ export const SecretOverviewPage = () => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const router = useRouter();
|
||||
const [scrollOffset, setScrollOffset] = useState(0);
|
||||
const [debouncedScrollOffset] = useDebounce(scrollOffset);
|
||||
// this is to set expandable table width
|
||||
// coz when overflow the table goes to the right
|
||||
const parentTableRef = useRef<HTMLTableElement>(null);
|
||||
const [expandableTableWidth, setExpandableTableWidth] = useState(0);
|
||||
const { permission } = useProjectPermission();
|
||||
|
||||
useEffect(() => {
|
||||
if (parentTableRef.current) {
|
||||
setExpandableTableWidth(parentTableRef.current.clientWidth);
|
||||
}
|
||||
}, [parentTableRef.current]);
|
||||
|
||||
const { currentWorkspace, isLoading: isWorkspaceLoading } = useWorkspace();
|
||||
const isProjectV3 = currentWorkspace?.version === ProjectVersion.V3;
|
||||
const { currentOrg } = useOrganization();
|
||||
@ -126,9 +133,8 @@ export const SecretOverviewPage = () => {
|
||||
>(new Map());
|
||||
|
||||
const [selectedEntries, setSelectedEntries] = useState<{
|
||||
// selectedEntries[name/key][envSlug][resource]
|
||||
[EntryType.FOLDER]: Record<string, Record<string, TSecretFolder>>;
|
||||
[EntryType.SECRET]: Record<string, Record<string, SecretV3RawSanitized>>;
|
||||
[EntryType.FOLDER]: Record<string, boolean>;
|
||||
[EntryType.SECRET]: Record<string, boolean>;
|
||||
}>({
|
||||
[EntryType.FOLDER]: {},
|
||||
[EntryType.SECRET]: {}
|
||||
@ -146,6 +152,23 @@ export const SecretOverviewPage = () => {
|
||||
orderBy
|
||||
} = usePagination<DashboardSecretsOrderBy>(DashboardSecretsOrderBy.Name);
|
||||
|
||||
const toggleSelectedEntry = useCallback(
|
||||
(type: EntryType, key: string) => {
|
||||
const isChecked = Boolean(selectedEntries[type]?.[key]);
|
||||
const newChecks = { ...selectedEntries };
|
||||
|
||||
// remove selection if its present else add it
|
||||
if (isChecked) {
|
||||
delete newChecks[type][key];
|
||||
} else {
|
||||
newChecks[type][key] = true;
|
||||
}
|
||||
|
||||
setSelectedEntries(newChecks);
|
||||
},
|
||||
[selectedEntries]
|
||||
);
|
||||
|
||||
const resetSelectedEntries = useCallback(() => {
|
||||
setSelectedEntries({
|
||||
[EntryType.FOLDER]: {},
|
||||
@ -154,13 +177,19 @@ export const SecretOverviewPage = () => {
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const handleParentTableWidthResize = () => {
|
||||
setExpandableTableWidth(parentTableRef.current?.clientWidth || 0);
|
||||
};
|
||||
|
||||
const onRouteChangeStart = () => {
|
||||
resetSelectedEntries();
|
||||
};
|
||||
|
||||
router.events.on("routeChangeStart", onRouteChangeStart);
|
||||
|
||||
window.addEventListener("resize", handleParentTableWidthResize);
|
||||
return () => {
|
||||
window.removeEventListener("resize", handleParentTableWidthResize);
|
||||
router.events.off("routeChangeStart", onRouteChangeStart);
|
||||
};
|
||||
}, []);
|
||||
@ -522,83 +551,6 @@ export const SecretOverviewPage = () => {
|
||||
[]
|
||||
);
|
||||
|
||||
const allRowsSelectedOnPage = useMemo(() => {
|
||||
if (
|
||||
(!secrets?.length ||
|
||||
secrets?.every((secret) => selectedEntries[EntryType.SECRET][secret.key])) &&
|
||||
(!folders?.length ||
|
||||
folders?.every((folder) => selectedEntries[EntryType.FOLDER][folder.name]))
|
||||
)
|
||||
return { isChecked: true, isIndeterminate: false };
|
||||
|
||||
if (
|
||||
secrets?.some((secret) => selectedEntries[EntryType.SECRET][secret.key]) ||
|
||||
folders?.some((folder) => selectedEntries[EntryType.FOLDER][folder.name])
|
||||
)
|
||||
return { isChecked: true, isIndeterminate: true };
|
||||
|
||||
return { isChecked: false, isIndeterminate: false };
|
||||
}, [selectedEntries, secrets, folders]);
|
||||
|
||||
const toggleSelectedEntry = useCallback(
|
||||
(type: EntryType, key: string) => {
|
||||
const isChecked = Boolean(selectedEntries[type]?.[key]);
|
||||
const newChecks = { ...selectedEntries };
|
||||
|
||||
// remove selection if its present else add it
|
||||
if (isChecked) {
|
||||
delete newChecks[type][key];
|
||||
} else {
|
||||
newChecks[type][key] = {};
|
||||
userAvailableEnvs.forEach((env) => {
|
||||
const resource =
|
||||
type === EntryType.SECRET
|
||||
? getSecretByKey(env.slug, key)
|
||||
: getFolderByNameAndEnv(key, env.slug);
|
||||
|
||||
if (resource) newChecks[type][key][env.slug] = resource;
|
||||
});
|
||||
}
|
||||
|
||||
setSelectedEntries(newChecks);
|
||||
},
|
||||
[selectedEntries, getFolderByNameAndEnv, getSecretByKey]
|
||||
);
|
||||
|
||||
const toggleSelectAllRows = () => {
|
||||
const newChecks = { ...selectedEntries };
|
||||
|
||||
userAvailableEnvs.forEach((env) => {
|
||||
secrets?.forEach((secret) => {
|
||||
if (allRowsSelectedOnPage.isChecked) {
|
||||
delete newChecks[EntryType.SECRET][secret.key];
|
||||
} else {
|
||||
if (!newChecks[EntryType.SECRET][secret.key])
|
||||
newChecks[EntryType.SECRET][secret.key] = {};
|
||||
|
||||
const resource = getSecretByKey(env.slug, secret.key);
|
||||
|
||||
if (resource) newChecks[EntryType.SECRET][secret.key][env.slug] = resource;
|
||||
}
|
||||
});
|
||||
|
||||
folders?.forEach((folder) => {
|
||||
if (allRowsSelectedOnPage.isChecked) {
|
||||
delete newChecks[EntryType.FOLDER][folder.name];
|
||||
} else {
|
||||
if (!newChecks[EntryType.FOLDER][folder.name])
|
||||
newChecks[EntryType.FOLDER][folder.name] = {};
|
||||
|
||||
const resource = getFolderByNameAndEnv(folder.name, env.slug);
|
||||
|
||||
if (resource) newChecks[EntryType.FOLDER][folder.name][env.slug] = resource;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
setSelectedEntries(newChecks);
|
||||
};
|
||||
|
||||
if (isWorkspaceLoading || (isProjectV3 && isOverviewLoading)) {
|
||||
return (
|
||||
<div className="container mx-auto flex h-screen w-full items-center justify-center px-8 text-mineshaft-50 dark:[color-scheme:dark]">
|
||||
@ -847,39 +799,18 @@ export const SecretOverviewPage = () => {
|
||||
</div>
|
||||
<SelectionPanel
|
||||
secretPath={secretPath}
|
||||
getSecretByKey={getSecretByKey}
|
||||
getFolderByNameAndEnv={getFolderByNameAndEnv}
|
||||
selectedEntries={selectedEntries}
|
||||
resetSelectedEntries={resetSelectedEntries}
|
||||
/>
|
||||
<div className="thin-scrollbar mt-4">
|
||||
<TableContainer
|
||||
onScroll={(e) => setScrollOffset(e.currentTarget.scrollLeft)}
|
||||
className="thin-scrollbar"
|
||||
>
|
||||
<div className="thin-scrollbar mt-4" ref={parentTableRef}>
|
||||
<TableContainer className="rounded-b-none">
|
||||
<Table>
|
||||
<THead>
|
||||
<Tr className="sticky top-0 z-20 border-0">
|
||||
<Th className="sticky left-0 z-20 min-w-[20rem] border-b-0 p-0">
|
||||
<div className="flex items-center border-b border-r border-mineshaft-600 pr-5 pl-3 pt-3.5 pb-3">
|
||||
<Tooltip
|
||||
className="max-w-[20rem] whitespace-nowrap capitalize"
|
||||
content={
|
||||
totalCount > 0
|
||||
? `${
|
||||
!allRowsSelectedOnPage.isChecked ? "Select" : "Unselect"
|
||||
} all folders and secrets on page`
|
||||
: ""
|
||||
}
|
||||
>
|
||||
<div className="mr-4 ml-2">
|
||||
<Checkbox
|
||||
isDisabled={totalCount === 0}
|
||||
id="checkbox-select-all-rows"
|
||||
isChecked={allRowsSelectedOnPage.isChecked}
|
||||
isIndeterminate={allRowsSelectedOnPage.isIndeterminate}
|
||||
onCheckedChange={toggleSelectAllRows}
|
||||
/>
|
||||
</div>
|
||||
</Tooltip>
|
||||
<div className="flex items-center border-b border-r border-mineshaft-600 px-5 pt-3.5 pb-3">
|
||||
Name
|
||||
<IconButton
|
||||
variant="plain"
|
||||
@ -1007,7 +938,7 @@ export const SecretOverviewPage = () => {
|
||||
<SecretOverviewFolderRow
|
||||
folderName={folderName}
|
||||
isFolderPresentInEnv={isFolderPresentInEnv}
|
||||
isSelected={Boolean(selectedEntries.folder[folderName])}
|
||||
isSelected={selectedEntries.folder[folderName]}
|
||||
onToggleFolderSelect={() =>
|
||||
toggleSelectedEntry(EntryType.FOLDER, folderName)
|
||||
}
|
||||
@ -1029,7 +960,7 @@ export const SecretOverviewPage = () => {
|
||||
))}
|
||||
{secKeys.map((key, index) => (
|
||||
<SecretOverviewTableRow
|
||||
isSelected={Boolean(selectedEntries.secret[key])}
|
||||
isSelected={selectedEntries.secret[key]}
|
||||
onToggleSecretSelect={() => toggleSelectedEntry(EntryType.SECRET, key)}
|
||||
secretPath={secretPath}
|
||||
getImportedSecretByKey={getImportedSecretByKey}
|
||||
@ -1041,7 +972,7 @@ export const SecretOverviewPage = () => {
|
||||
environments={visibleEnvs}
|
||||
secretKey={key}
|
||||
getSecretByKey={getSecretByKey}
|
||||
scrollOffset={debouncedScrollOffset}
|
||||
expandableColWidth={expandableTableWidth}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
|
@ -23,6 +23,7 @@ type Props = {
|
||||
secretKey: string;
|
||||
secretPath: string;
|
||||
environments: { name: string; slug: string }[];
|
||||
expandableColWidth: number;
|
||||
isSelected: boolean;
|
||||
onToggleSecretSelect: (key: string) => void;
|
||||
getSecretByKey: (slug: string, key: string) => SecretV3RawSanitized | undefined;
|
||||
@ -40,7 +41,6 @@ type Props = {
|
||||
env: string,
|
||||
secretName: string
|
||||
) => { secret?: SecretV3RawSanitized; environmentInfo?: WorkspaceEnv } | undefined;
|
||||
scrollOffset: number;
|
||||
};
|
||||
|
||||
export const SecretOverviewTableRow = ({
|
||||
@ -53,7 +53,9 @@ export const SecretOverviewTableRow = ({
|
||||
onSecretDelete,
|
||||
isImportedSecretPresentInEnv,
|
||||
getImportedSecretByKey,
|
||||
scrollOffset,
|
||||
// temporary until below todo is resolved
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
expandableColWidth,
|
||||
onToggleSecretSelect,
|
||||
isSelected
|
||||
}: Props) => {
|
||||
@ -150,11 +152,11 @@ export const SecretOverviewTableRow = ({
|
||||
}`}
|
||||
>
|
||||
<div
|
||||
className="ml-2 p-2"
|
||||
style={{
|
||||
marginLeft: scrollOffset,
|
||||
width: "calc(100vw - 290px)" // 290px accounts for sidebar and margin
|
||||
}}
|
||||
className="ml-2 w-[99%] p-2"
|
||||
// TODO: scott expandableColWidth sometimes 0 due to parent ref not mounting, opting for relative width until resolved
|
||||
// style={{
|
||||
// width: `calc(${expandableColWidth} - 1rem)`
|
||||
// }}
|
||||
>
|
||||
<SecretRenameRow
|
||||
secretKey={secretKey}
|
||||
|
@ -27,23 +27,31 @@ export enum EntryType {
|
||||
|
||||
type Props = {
|
||||
secretPath: string;
|
||||
getSecretByKey: (slug: string, key: string) => SecretV3RawSanitized | undefined;
|
||||
getFolderByNameAndEnv: (name: string, env: string) => TSecretFolder | undefined;
|
||||
resetSelectedEntries: () => void;
|
||||
selectedEntries: {
|
||||
[EntryType.FOLDER]: Record<string, Record<string, TSecretFolder>>;
|
||||
[EntryType.SECRET]: Record<string, Record<string, SecretV3RawSanitized>>;
|
||||
[EntryType.FOLDER]: Record<string, boolean>;
|
||||
[EntryType.SECRET]: Record<string, boolean>;
|
||||
};
|
||||
};
|
||||
|
||||
export const SelectionPanel = ({ secretPath, resetSelectedEntries, selectedEntries }: Props) => {
|
||||
export const SelectionPanel = ({
|
||||
getFolderByNameAndEnv,
|
||||
getSecretByKey,
|
||||
secretPath,
|
||||
resetSelectedEntries,
|
||||
selectedEntries
|
||||
}: Props) => {
|
||||
const { permission } = useProjectPermission();
|
||||
|
||||
const { handlePopUpOpen, handlePopUpToggle, handlePopUpClose, popUp } = usePopUp([
|
||||
"bulkDeleteEntries"
|
||||
] as const);
|
||||
|
||||
const selectedFolderCount = Object.keys(selectedEntries.folder).length;
|
||||
const selectedKeysCount = Object.keys(selectedEntries.secret).length;
|
||||
const selectedCount = selectedFolderCount + selectedKeysCount;
|
||||
const selectedFolderCount = Object.keys(selectedEntries.folder).length
|
||||
const selectedKeysCount = Object.keys(selectedEntries.secret).length
|
||||
const selectedCount = selectedFolderCount + selectedKeysCount
|
||||
|
||||
const { currentWorkspace } = useWorkspace();
|
||||
const workspaceId = currentWorkspace?.id || "";
|
||||
@ -69,7 +77,7 @@ export const SelectionPanel = ({ secretPath, resetSelectedEntries, selectedEntri
|
||||
return "Do you want to delete the selected secrets across environments?";
|
||||
}
|
||||
return "Do you want to delete the selected folders across environments?";
|
||||
};
|
||||
}
|
||||
|
||||
const handleBulkDelete = async () => {
|
||||
let processedEntries = 0;
|
||||
@ -86,8 +94,8 @@ export const SelectionPanel = ({ secretPath, resetSelectedEntries, selectedEntri
|
||||
}
|
||||
|
||||
await Promise.all(
|
||||
Object.values(selectedEntries.folder).map(async (folderRecord) => {
|
||||
const folder = folderRecord[env.slug];
|
||||
Object.keys(selectedEntries.folder).map(async (folderName) => {
|
||||
const folder = getFolderByNameAndEnv(folderName, env.slug);
|
||||
if (folder) {
|
||||
processedEntries += 1;
|
||||
await deleteFolder({
|
||||
@ -100,9 +108,9 @@ export const SelectionPanel = ({ secretPath, resetSelectedEntries, selectedEntri
|
||||
})
|
||||
);
|
||||
|
||||
const secretsToDelete = Object.values(selectedEntries.secret).reduce(
|
||||
(accum: TDeleteSecretBatchDTO["secrets"], secretRecord) => {
|
||||
const entry = secretRecord[env.slug];
|
||||
const secretsToDelete = Object.keys(selectedEntries.secret).reduce(
|
||||
(accum: TDeleteSecretBatchDTO["secrets"], secretName) => {
|
||||
const entry = getSecretByKey(env.slug, secretName);
|
||||
if (entry) {
|
||||
return [
|
||||
...accum,
|
||||
@ -165,7 +173,7 @@ export const SelectionPanel = ({ secretPath, resetSelectedEntries, selectedEntri
|
||||
<FontAwesomeIcon icon={faMinusSquare} size="lg" />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
<div className="ml-1 flex-grow px-2 text-sm">{selectedCount} Selected</div>
|
||||
<div className="ml-4 flex-grow px-2 text-sm">{selectedCount} Selected</div>
|
||||
{shouldShowDelete && (
|
||||
<Button
|
||||
variant="outline_bg"
|
||||
|
@ -13,9 +13,9 @@ type: application
|
||||
# This is the chart version. This version number should be incremented each time you make changes
|
||||
# to the chart and its templates, including the app version.
|
||||
# Versions are expected to follow Semantic Versioning (https://semver.org/)
|
||||
version: v0.7.2
|
||||
version: v0.7.3
|
||||
# This is the version number of the application being deployed. This version number should be
|
||||
# incremented each time you make changes to the application. Versions are not expected to
|
||||
# follow Semantic Versioning. They should reflect the version the application is using.
|
||||
# It is recommended to use it with quotes.
|
||||
appVersion: "v0.7.2"
|
||||
appVersion: "v0.7.3"
|
||||
|
@ -32,7 +32,7 @@ controllerManager:
|
||||
- ALL
|
||||
image:
|
||||
repository: infisical/kubernetes-operator
|
||||
tag: v0.7.2
|
||||
tag: v0.7.3
|
||||
resources:
|
||||
limits:
|
||||
cpu: 500m
|
||||
|
@ -39,6 +39,7 @@ type InfisicalSecretReconciler struct {
|
||||
|
||||
type ResourceVariables struct {
|
||||
infisicalClient infisicalSdk.InfisicalClientInterface
|
||||
cancelCtx context.CancelFunc
|
||||
authDetails AuthenticationDetails
|
||||
}
|
||||
|
||||
@ -136,11 +137,17 @@ func (r *InfisicalSecretReconciler) SetupWithManager(mgr ctrl.Manager) error {
|
||||
return ctrl.NewControllerManagedBy(mgr).
|
||||
For(&secretsv1alpha1.InfisicalSecret{}, builder.WithPredicates(predicate.Funcs{
|
||||
UpdateFunc: func(e event.UpdateEvent) bool {
|
||||
delete(resourceVariablesMap, string(e.ObjectNew.GetUID()))
|
||||
if rv, ok := resourceVariablesMap[string(e.ObjectNew.GetUID())]; ok {
|
||||
rv.cancelCtx()
|
||||
delete(resourceVariablesMap, string(e.ObjectNew.GetUID()))
|
||||
}
|
||||
return true
|
||||
},
|
||||
DeleteFunc: func(e event.DeleteEvent) bool {
|
||||
delete(resourceVariablesMap, string(e.Object.GetUID()))
|
||||
if rv, ok := resourceVariablesMap[string(e.Object.GetUID())]; ok {
|
||||
rv.cancelCtx()
|
||||
delete(resourceVariablesMap, string(e.Object.GetUID()))
|
||||
}
|
||||
return true
|
||||
},
|
||||
})).
|
||||
|
@ -293,13 +293,16 @@ func (r *InfisicalSecretReconciler) GetResourceVariables(infisicalSecret v1alpha
|
||||
|
||||
if _, ok := resourceVariablesMap[string(infisicalSecret.UID)]; !ok {
|
||||
|
||||
client := infisicalSdk.NewInfisicalClient(infisicalSdk.Config{
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
|
||||
client := infisicalSdk.NewInfisicalClient(ctx, infisicalSdk.Config{
|
||||
SiteUrl: api.API_HOST_URL,
|
||||
UserAgent: api.USER_AGENT_NAME,
|
||||
})
|
||||
|
||||
resourceVariablesMap[string(infisicalSecret.UID)] = ResourceVariables{
|
||||
infisicalClient: client,
|
||||
cancelCtx: cancel,
|
||||
authDetails: AuthenticationDetails{},
|
||||
}
|
||||
|
||||
@ -321,6 +324,7 @@ func (r *InfisicalSecretReconciler) ReconcileInfisicalSecret(ctx context.Context
|
||||
|
||||
resourceVariables := r.GetResourceVariables(infisicalSecret)
|
||||
infisicalClient := resourceVariables.infisicalClient
|
||||
cancelCtx := resourceVariables.cancelCtx
|
||||
authDetails := resourceVariables.authDetails
|
||||
var err error
|
||||
|
||||
@ -335,6 +339,7 @@ func (r *InfisicalSecretReconciler) ReconcileInfisicalSecret(ctx context.Context
|
||||
|
||||
r.UpdateResourceVariables(infisicalSecret, ResourceVariables{
|
||||
infisicalClient: infisicalClient,
|
||||
cancelCtx: cancelCtx,
|
||||
authDetails: authDetails,
|
||||
})
|
||||
}
|
||||
|
@ -3,7 +3,7 @@ module github.com/Infisical/infisical/k8-operator
|
||||
go 1.21
|
||||
|
||||
require (
|
||||
github.com/infisical/go-sdk v0.3.2
|
||||
github.com/infisical/go-sdk v0.3.7
|
||||
github.com/onsi/ginkgo/v2 v2.6.0
|
||||
github.com/onsi/gomega v1.24.1
|
||||
k8s.io/apimachinery v0.26.1
|
||||
@ -12,10 +12,10 @@ require (
|
||||
)
|
||||
|
||||
require (
|
||||
cloud.google.com/go/auth v0.6.1 // indirect
|
||||
cloud.google.com/go/auth v0.7.0 // indirect
|
||||
cloud.google.com/go/auth/oauth2adapt v0.2.2 // indirect
|
||||
cloud.google.com/go/compute/metadata v0.4.0 // indirect
|
||||
cloud.google.com/go/iam v1.1.10 // indirect
|
||||
cloud.google.com/go/iam v1.1.11 // indirect
|
||||
github.com/aws/aws-sdk-go-v2 v1.30.1 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/config v1.27.24 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.17.24 // indirect
|
||||
@ -41,7 +41,7 @@ require (
|
||||
go.opentelemetry.io/otel/metric v1.28.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.28.0 // indirect
|
||||
golang.org/x/sync v0.7.0 // indirect
|
||||
google.golang.org/api v0.187.0 // indirect
|
||||
google.golang.org/api v0.188.0 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240708141625-4ad9e859172b // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240708141625-4ad9e859172b // indirect
|
||||
google.golang.org/grpc v1.65.0 // indirect
|
||||
|
@ -13,8 +13,8 @@ cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKV
|
||||
cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=
|
||||
cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=
|
||||
cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=
|
||||
cloud.google.com/go/auth v0.6.1 h1:T0Zw1XM5c1GlpN2HYr2s+m3vr1p2wy+8VN+Z1FKxW38=
|
||||
cloud.google.com/go/auth v0.6.1/go.mod h1:eFHG7zDzbXHKmjJddFG/rBlcGp6t25SwRUiEQSlO4x4=
|
||||
cloud.google.com/go/auth v0.7.0 h1:kf/x9B3WTbBUHkC+1VS8wwwli9TzhSt0vSTVBmMR8Ts=
|
||||
cloud.google.com/go/auth v0.7.0/go.mod h1:D+WqdrpcjmiCgWrXmLLxOVq1GACoE36chW6KXoEvuIw=
|
||||
cloud.google.com/go/auth/oauth2adapt v0.2.2 h1:+TTV8aXpjeChS9M+aTtN/TjdQnzJvmzKFt//oWu7HX4=
|
||||
cloud.google.com/go/auth/oauth2adapt v0.2.2/go.mod h1:wcYjgpZI9+Yu7LyYBg4pqSiaRkfEK3GQcpb7C/uyF1Q=
|
||||
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
|
||||
@ -27,8 +27,8 @@ cloud.google.com/go/compute/metadata v0.4.0 h1:vHzJCWaM4g8XIcm8kopr3XmDA4Gy/lblD
|
||||
cloud.google.com/go/compute/metadata v0.4.0/go.mod h1:SIQh1Kkb4ZJ8zJ874fqVkslA29PRXuleyj6vOzlbK7M=
|
||||
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
|
||||
cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
|
||||
cloud.google.com/go/iam v1.1.10 h1:ZSAr64oEhQSClwBL670MsJAW5/RLiC6kfw3Bqmd5ZDI=
|
||||
cloud.google.com/go/iam v1.1.10/go.mod h1:iEgMq62sg8zx446GCaijmA2Miwg5o3UbO+nI47WHJps=
|
||||
cloud.google.com/go/iam v1.1.11 h1:0mQ8UKSfdHLut6pH9FM3bI55KWR46ketn0PuXleDyxw=
|
||||
cloud.google.com/go/iam v1.1.11/go.mod h1:biXoiLWYIKntto2joP+62sd9uW5EpkZmKIvfNcTWlnQ=
|
||||
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
|
||||
cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
|
||||
cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
|
||||
@ -217,8 +217,8 @@ github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||
github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU=
|
||||
github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
|
||||
github.com/infisical/go-sdk v0.3.2 h1:BfeQzG7s3qmEGhgXu0d1YNsyaiHucHgI+BaLpx+W8cc=
|
||||
github.com/infisical/go-sdk v0.3.2/go.mod h1:vHTDVw3k+wfStXab513TGk1n53kaKF2xgLqpw/xvtl4=
|
||||
github.com/infisical/go-sdk v0.3.7 h1:EE0ALjjdJtNvDzHtxotkBxYZ6L9ZmeruH89u6jh1Bik=
|
||||
github.com/infisical/go-sdk v0.3.7/go.mod h1:HHW7DgUqoolyQIUw/9HdpkZ3bDLwWyZ0HEtYiVaDKQw=
|
||||
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
||||
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
|
||||
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
||||
@ -601,8 +601,8 @@ google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0M
|
||||
google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
|
||||
google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=
|
||||
google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
|
||||
google.golang.org/api v0.187.0 h1:Mxs7VATVC2v7CY+7Xwm4ndkX71hpElcvx0D1Ji/p1eo=
|
||||
google.golang.org/api v0.187.0/go.mod h1:KIHlTc4x7N7gKKuVsdmfBXN13yEEWXWFURWY6SBp2gk=
|
||||
google.golang.org/api v0.188.0 h1:51y8fJ/b1AaaBRJr4yWm96fPcuxSo0JcegXE3DaHQHw=
|
||||
google.golang.org/api v0.188.0/go.mod h1:VR0d+2SIiWOYG3r/jdm7adPW9hI2aRv9ETOSCQ9Beag=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
|
Reference in New Issue
Block a user