mirror of
https://github.com/Infisical/infisical.git
synced 2025-07-25 14:07:47 +00:00
Compare commits
4 Commits
misc/expor
...
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
|
||||
|
||||
|
@@ -58,7 +58,6 @@
|
||||
"migration:latest": "npm run auditlog-migration:latest && knex --knexfile ./src/db/knexfile.ts --client pg migrate:latest",
|
||||
"migration:status": "npm run auditlog-migration:status && knex --knexfile ./src/db/knexfile.ts --client pg migrate:status",
|
||||
"migration:rollback": "npm run auditlog-migration:rollback && knex --knexfile ./src/db/knexfile.ts migrate:rollback",
|
||||
"migrate:org": "tsx ./scripts/migrate-organization.ts",
|
||||
"seed:new": "tsx ./scripts/create-seed-file.ts",
|
||||
"seed": "knex --knexfile ./src/db/knexfile.ts --client pg seed:run",
|
||||
"db:reset": "npm run migration:rollback -- --all && npm run migration:latest"
|
||||
|
@@ -1,84 +0,0 @@
|
||||
/* eslint-disable */
|
||||
import promptSync from "prompt-sync";
|
||||
import { execSync } from "child_process";
|
||||
import path from "path";
|
||||
import { existsSync } from "fs";
|
||||
|
||||
const prompt = promptSync({
|
||||
sigint: true
|
||||
});
|
||||
|
||||
const exportDb = () => {
|
||||
const exportHost = prompt("Enter your Postgres Host to migrate from: ");
|
||||
const exportPort = prompt("Enter your Postgres Port to migrate from [Default = 5432]: ") ?? "5432";
|
||||
const exportUser = prompt("Enter your Postgres User to migrate from: [Default = infisical]: ") ?? "infisical";
|
||||
const exportPassword = prompt("Enter your Postgres Password to migrate from: ");
|
||||
const exportDatabase = prompt("Enter your Postgres Database to migrate from [Default = infisical]: ") ?? "infisical";
|
||||
|
||||
// we do not include the audit_log and secret_sharing entries
|
||||
execSync(
|
||||
`PGDATABASE="${exportDatabase}" PGPASSWORD="${exportPassword}" PGHOST="${exportHost}" PGPORT=${exportPort} PGUSER=${exportUser} pg_dump infisical --exclude-table-data="secret_sharing" --exclude-table-data="audit_log*" > ${path.join(
|
||||
__dirname,
|
||||
"../src/db/dump.sql"
|
||||
)}`,
|
||||
{ stdio: "inherit" }
|
||||
);
|
||||
};
|
||||
|
||||
const importDbForOrg = () => {
|
||||
const importHost = prompt("Enter your Postgres Host to migrate to: ");
|
||||
const importPort = prompt("Enter your Postgres Port to migrate to [Default = 5432]: ") ?? "5432";
|
||||
const importUser = prompt("Enter your Postgres User to migrate to: [Default = infisical]: ") ?? "infisical";
|
||||
const importPassword = prompt("Enter your Postgres Password to migrate to: ");
|
||||
const importDatabase = prompt("Enter your Postgres Database to migrate to [Default = infisical]: ") ?? "infisical";
|
||||
const orgId = prompt("Enter the organization ID to migrate: ");
|
||||
|
||||
if (!existsSync(path.join(__dirname, "../src/db/dump.sql"))) {
|
||||
console.log("File not found, please export the database first.");
|
||||
return;
|
||||
}
|
||||
|
||||
execSync(
|
||||
`PGDATABASE="${importDatabase}" PGPASSWORD="${importPassword}" PGHOST="${importHost}" PGPORT=${importPort} PGUSER=${importUser} psql -f ${path.join(
|
||||
__dirname,
|
||||
"../src/db/dump.sql"
|
||||
)}`
|
||||
);
|
||||
|
||||
execSync(
|
||||
`PGDATABASE="${importDatabase}" PGPASSWORD="${importPassword}" PGHOST="${importHost}" PGPORT=${importPort} PGUSER=${importUser} psql -c "DELETE FROM public.organizations WHERE id != '${orgId}'"`
|
||||
);
|
||||
|
||||
// delete global/instance-level resources not relevant to the organization to migrate
|
||||
// users
|
||||
execSync(
|
||||
`PGDATABASE="${importDatabase}" PGPASSWORD="${importPassword}" PGHOST="${importHost}" PGPORT=${importPort} PGUSER=${importUser} psql -c 'DELETE FROM users WHERE users.id NOT IN (SELECT org_memberships."userId" FROM org_memberships)'`
|
||||
);
|
||||
|
||||
// identities
|
||||
execSync(
|
||||
`PGDATABASE="${importDatabase}" PGPASSWORD="${importPassword}" PGHOST="${importHost}" PGPORT=${importPort} PGUSER=${importUser} psql -c 'DELETE FROM identities WHERE id NOT IN (SELECT "identityId" FROM identity_org_memberships)'`
|
||||
);
|
||||
|
||||
// reset slack configuration in superAdmin
|
||||
execSync(
|
||||
`PGDATABASE="${importDatabase}" PGPASSWORD="${importPassword}" PGHOST="${importHost}" PGPORT=${importPort} PGUSER=${importUser} psql -c 'UPDATE super_admin SET "encryptedSlackClientId" = null, "encryptedSlackClientSecret" = null'`
|
||||
);
|
||||
|
||||
console.log("Organization migrated successfully.");
|
||||
};
|
||||
|
||||
const main = () => {
|
||||
const action = prompt(
|
||||
"Enter the action to perform\n 1. Export from existing instance.\n 2. Import org to instance.\n \n Action: "
|
||||
);
|
||||
if (action === "1") {
|
||||
exportDb();
|
||||
} else if (action === "2") {
|
||||
importDbForOrg();
|
||||
} else {
|
||||
console.log("Invalid action");
|
||||
}
|
||||
};
|
||||
|
||||
main();
|
@@ -1,21 +0,0 @@
|
||||
import { Knex } from "knex";
|
||||
|
||||
import { TableName } from "../schemas";
|
||||
|
||||
export async function up(knex: Knex): Promise<void> {
|
||||
if (await knex.schema.hasColumn(TableName.SamlConfig, "orgId")) {
|
||||
await knex.schema.alterTable(TableName.SamlConfig, (t) => {
|
||||
t.dropForeign("orgId");
|
||||
t.foreign("orgId").references("id").inTable(TableName.Organization).onDelete("CASCADE");
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export async function down(knex: Knex): Promise<void> {
|
||||
if (await knex.schema.hasColumn(TableName.SamlConfig, "orgId")) {
|
||||
await knex.schema.alterTable(TableName.SamlConfig, (t) => {
|
||||
t.dropForeign("orgId");
|
||||
t.foreign("orgId").references("id").inTable(TableName.Organization);
|
||||
});
|
||||
}
|
||||
}
|
@@ -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"
|
||||
]
|
||||
}
|
||||
]
|
||||
|
@@ -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