Merge branch 'heads/main' into daniel/srp-removal-round-2

This commit is contained in:
Daniel Hougaard
2025-07-30 04:44:57 +04:00
474 changed files with 5372 additions and 28647 deletions
+16 -24
View File
@@ -55,7 +55,7 @@
"@ucast/mongo2js": "^1.3.4",
"@xyflow/react": "^12.4.4",
"argon2-browser": "^1.18.0",
"axios": "^1.7.9",
"axios": "^1.11.0",
"classnames": "^2.5.1",
"cva": "npm:class-variance-authority@^0.7.1",
"date-fns": "^4.1.0",
@@ -5282,13 +5282,13 @@
}
},
"node_modules/axios": {
"version": "1.8.3",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.8.3.tgz",
"integrity": "sha512-iP4DebzoNlP/YN2dpwCgb8zoCmhtkajzS48JvwmkSkXvPI3DHc7m+XYL5tGnSlJtR6nImXZmdCuN5aP8dh1d8A==",
"version": "1.11.0",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.11.0.tgz",
"integrity": "sha512-1Lx3WLFQWm3ooKDYZD1eXmoGO9fxYQjrycfHFC8P0sCfQVXyROp0p9PFWBehewBOdCwHc+f/b8I0fMto5eSfwA==",
"license": "MIT",
"dependencies": {
"follow-redirects": "^1.15.6",
"form-data": "^4.0.0",
"form-data": "^4.0.4",
"proxy-from-env": "^1.1.0"
}
},
@@ -5700,7 +5700,6 @@
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.1.tgz",
"integrity": "sha512-BhYE+WDaywFg2TBWYNXAE+8B1ATnThNBqXHP5nQu0jWJdVvY2hvkpyB3qOmtmDePiS5/BDQ8wASEWGMWRG148g==",
"dev": true,
"license": "MIT",
"dependencies": {
"es-errors": "^1.3.0",
@@ -6665,7 +6664,6 @@
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.0.tgz",
"integrity": "sha512-9+Sj30DIu+4KvHqMfLUGLFYL2PkURSYMVXJyXe92nFRvlYq5hBjLEhblKB+vkd/WVlUYMWigiY07T91Fkk0+4A==",
"dev": true,
"license": "MIT",
"dependencies": {
"call-bind-apply-helpers": "^1.0.0",
@@ -6819,7 +6817,6 @@
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
"integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 0.4"
@@ -6829,7 +6826,6 @@
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 0.4"
@@ -6867,7 +6863,6 @@
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.0.0.tgz",
"integrity": "sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw==",
"dev": true,
"license": "MIT",
"dependencies": {
"es-errors": "^1.3.0"
@@ -6877,15 +6872,15 @@
}
},
"node_modules/es-set-tostringtag": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.3.tgz",
"integrity": "sha512-3T8uNMC3OQTHkFUsFq8r/BwAXLHvU/9O9mE0fBc/MY5iq/8H7ncvO947LmYA6ldWw9Uh8Yhf25zu6n7nML5QWQ==",
"dev": true,
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
"integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
"license": "MIT",
"dependencies": {
"get-intrinsic": "^1.2.4",
"es-errors": "^1.3.0",
"get-intrinsic": "^1.2.6",
"has-tostringtag": "^1.0.2",
"hasown": "^2.0.1"
"hasown": "^2.0.2"
},
"engines": {
"node": ">= 0.4"
@@ -7855,13 +7850,15 @@
}
},
"node_modules/form-data": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.1.tgz",
"integrity": "sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==",
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz",
"integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==",
"license": "MIT",
"dependencies": {
"asynckit": "^0.4.0",
"combined-stream": "^1.0.8",
"es-set-tostringtag": "^2.1.0",
"hasown": "^2.0.2",
"mime-types": "^2.1.12"
},
"engines": {
@@ -7992,7 +7989,6 @@
"version": "1.2.6",
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.6.tgz",
"integrity": "sha512-qxsEs+9A+u85HhllWJJFicJfPDhRmjzoYdl64aMWW9yRIJmSyxdn8IEkuIM530/7T+lv0TIHd8L6Q/ra0tEoeA==",
"dev": true,
"license": "MIT",
"dependencies": {
"call-bind-apply-helpers": "^1.0.1",
@@ -8139,7 +8135,6 @@
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
"integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 0.4"
@@ -8215,7 +8210,6 @@
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
"integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 0.4"
@@ -8228,7 +8222,6 @@
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
"integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
"dev": true,
"license": "MIT",
"dependencies": {
"has-symbols": "^1.0.3"
@@ -9545,7 +9538,6 @@
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.0.0.tgz",
"integrity": "sha512-4MqMiKP90ybymYvsut0CH2g4XWbfLtmlCkXmtmdcDCxNB+mQcu1w/1+L/VD7vi/PSv7X2JYV7SCcR+jiPXnQtA==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 0.4"
+1 -1
View File
@@ -59,7 +59,7 @@
"@ucast/mongo2js": "^1.3.4",
"@xyflow/react": "^12.4.4",
"argon2-browser": "^1.18.0",
"axios": "^1.7.9",
"axios": "^1.11.0",
"classnames": "^2.5.1",
"cva": "npm:class-variance-authority@^0.7.1",
"date-fns": "^4.1.0",
@@ -1,26 +1,14 @@
import { FunctionComponent, ReactNode } from "react";
import { BoundCanProps, Can } from "@casl/react";
import { faLock } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { TOrgPermission, useOrgPermission } from "@app/context/OrgPermissionContext";
import { Tooltip } from "../v2";
import { AccessRestrictedBanner, Tooltip } from "../v2";
export const OrgPermissionGuardBanner = () => {
return (
<div className="container mx-auto flex h-full items-center justify-center">
<div className="flex items-end space-x-12 rounded-md bg-mineshaft-800 p-16 text-bunker-300">
<div>
<FontAwesomeIcon icon={faLock} size="6x" />
</div>
<div>
<div className="mb-2 text-4xl font-medium">Access Restricted</div>
<div className="text-sm">
Your role has limited permissions, please <br /> contact your admin to gain access
</div>
</div>
</div>
<AccessRestrictedBanner />
</div>
);
};
@@ -1,15 +1,12 @@
import { ReactNode } from "react";
import { faLock } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { twMerge } from "tailwind-merge";
import { AccessRestrictedBanner } from "@app/components/v2";
type Props = {
containerClassName?: string;
className?: string;
children?: ReactNode;
};
export const PermissionDeniedBanner = ({ containerClassName, className, children }: Props) => {
export const PermissionDeniedBanner = ({ containerClassName }: Props) => {
return (
<div
className={twMerge(
@@ -17,22 +14,7 @@ export const PermissionDeniedBanner = ({ containerClassName, className, children
containerClassName
)}
>
<div className={twMerge("rounded-md bg-mineshaft-800 p-16 text-bunker-300", className)}>
<div className="flex items-end space-x-12">
<div>
<FontAwesomeIcon icon={faLock} size="6x" />
</div>
<div>
<div className="mb-2 text-4xl font-medium">Access Restricted</div>
{children || (
<div className="text-sm">
Your role has limited permissions, please <br /> contact your administrator to gain
access
</div>
)}
</div>
</div>
</div>
<AccessRestrictedBanner />
</div>
);
};
@@ -1,9 +1,8 @@
import { FunctionComponent, ReactNode } from "react";
import { AbilityTuple, MongoAbility } from "@casl/ability";
import { Can } from "@casl/react";
import { faLock } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { AccessRestrictedBanner } from "@app/components/v2";
import { ProjectPermissionSet, useProjectPermission } from "@app/context/ProjectPermissionContext";
import { Tooltip } from "../v2/Tooltip";
@@ -11,17 +10,7 @@ import { Tooltip } from "../v2/Tooltip";
export const ProjectPermissionGuardBanner = () => {
return (
<div className="container mx-auto flex h-full items-center justify-center">
<div className="flex items-end space-x-12 rounded-md bg-mineshaft-800 p-16 text-bunker-300">
<div>
<FontAwesomeIcon icon={faLock} size="6x" />
</div>
<div>
<div className="mb-2 text-4xl font-medium">Access Restricted</div>
<div className="text-sm">
Your role has limited permissions, please <br /> contact your admin to gain access
</div>
</div>
</div>
<AccessRestrictedBanner />
</div>
);
};
@@ -271,6 +271,8 @@ const NewProjectForm = ({ onOpenChange }: NewProjectFormProps) => {
value={value}
onValueChange={onChange}
className="w-full"
position="popper"
dropdownContainerClassName="max-w-none"
>
{projectTemplates.length
? projectTemplates.map((template) => (
@@ -306,6 +308,9 @@ const NewProjectForm = ({ onOpenChange }: NewProjectFormProps) => {
onChange(e);
}}
className="mb-12 w-full bg-mineshaft-600"
position="popper"
dropdownContainerClassName="max-w-none -top-1"
side="top"
>
<SelectItem value={INTERNAL_KMS_KEY_ID} key="kms-internal">
Default Infisical KMS
@@ -76,7 +76,6 @@ export const CreateSecretRotationV2Modal = ({ onOpenChange, isOpen, ...props }:
</div>
)
}
onPointerDownOutside={(e) => e.preventDefault()}
className={selectedRotation ? "max-w-2xl" : "max-w-3xl"}
subTitle={
selectedRotation ? undefined : "Select a provider to create a secret rotation for."
@@ -75,7 +75,6 @@ export const CreateSecretScanningDataSourceModal = ({ onOpenChange, isOpen, ...p
</div>
)
}
onPointerDownOutside={(e) => e.preventDefault()}
className={selectedDataSource ? "max-w-2xl" : "max-w-3xl"}
subTitle={
selectedDataSource ? undefined : "Select a data source to configure secret scanning for."
@@ -56,7 +56,6 @@ export const CreateSecretSyncModal = ({ onOpenChange, selectSync = null, ...prop
"Add Sync"
)
}
onPointerDownOutside={(e) => e.preventDefault()}
className="max-w-2xl"
bodyClassName="overflow-visible"
subTitle={selectedSync ? undefined : "Select a third-party service to sync secrets to."}
@@ -9,7 +9,7 @@ import {
useRenderConnectionListServices
} from "@app/hooks/api/appConnections/render";
import { SecretSync } from "@app/hooks/api/secretSyncs";
import { RenderSyncScope, RenderSyncType } from "@app/hooks/api/secretSyncs/render-sync";
import { RenderSyncScope, RenderSyncType } from "@app/hooks/api/secretSyncs/types/render-sync";
import { TSecretSyncForm } from "../schemas";
@@ -0,0 +1,40 @@
import { Controller, useFormContext } from "react-hook-form";
import { faQuestionCircle } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { FormControl, Switch, Tooltip } from "@app/components/v2";
import { SecretSync } from "@app/hooks/api/secretSyncs";
import { TSecretSyncForm } from "../schemas";
export const RenderSyncOptionsFields = () => {
const { control } = useFormContext<TSecretSyncForm & { destination: SecretSync.Render }>();
return (
<Controller
name="syncOptions.autoRedeployServices"
control={control}
render={({ field: { value, onChange }, fieldState: { error } }) => (
<FormControl className="mt-4" isError={Boolean(error?.message)} errorText={error?.message}>
<Switch
className="bg-mineshaft-400/50 shadow-inner data-[state=checked]:bg-green/80"
id="auto-redeploy-services"
thumbClassName="bg-mineshaft-800"
isChecked={value}
onCheckedChange={onChange}
>
Auto Redeploy Services On Sync
<Tooltip
className="max-w-md"
content={
<p>If enabled, services will be automatically redeployed upon secret changes.</p>
}
>
<FontAwesomeIcon icon={faQuestionCircle} size="sm" className="ml-1" />
</Tooltip>
</Switch>
</FormControl>
)}
/>
);
};
@@ -14,6 +14,7 @@ import { SecretSync, useSecretSyncOption } from "@app/hooks/api/secretSyncs";
import { TSecretSyncForm } from "../schemas";
import { AwsParameterStoreSyncOptionsFields } from "./AwsParameterStoreSyncOptionsFields";
import { AwsSecretsManagerSyncOptionsFields } from "./AwsSecretsManagerSyncOptionsFields";
import { RenderSyncOptionsFields } from "./RenderSyncOptionsFields";
type Props = {
hideInitialSync?: boolean;
@@ -38,6 +39,9 @@ export const SecretSyncOptionsFields = ({ hideInitialSync }: Props) => {
case SecretSync.AWSSecretsManager:
AdditionalSyncOptionsFieldsComponent = <AwsSecretsManagerSyncOptionsFields />;
break;
case SecretSync.Render:
AdditionalSyncOptionsFieldsComponent = <RenderSyncOptionsFields />;
break;
case SecretSync.GitHub:
case SecretSync.GCPSecretManager:
case SecretSync.AzureKeyVault:
@@ -54,7 +58,6 @@ export const SecretSyncOptionsFields = ({ hideInitialSync }: Props) => {
case SecretSync.OnePass:
case SecretSync.OCIVault:
case SecretSync.Heroku:
case SecretSync.Render:
case SecretSync.Flyio:
case SecretSync.GitLab:
case SecretSync.CloudflarePages:
@@ -2,8 +2,29 @@ import { useFormContext } from "react-hook-form";
import { GenericFieldLabel } from "@app/components/secret-syncs";
import { TSecretSyncForm } from "@app/components/secret-syncs/forms/schemas";
import { Badge } from "@app/components/v2";
import { SecretSync } from "@app/hooks/api/secretSyncs";
export const RenderSyncOptionsReviewFields = () => {
const { watch } = useFormContext<TSecretSyncForm & { destination: SecretSync.Render }>();
const [{ autoRedeployServices }] = watch(["syncOptions"]);
return (
<div>
{autoRedeployServices ? (
<GenericFieldLabel label="Auto Redeploy Services">
<Badge variant="success">Enabled</Badge>
</GenericFieldLabel>
) : (
<GenericFieldLabel label="Auto Redeploy Services">
<Badge variant="danger">Disabled</Badge>
</GenericFieldLabel>
)}
</div>
);
};
export const RenderSyncReviewFields = () => {
const { watch } = useFormContext<TSecretSyncForm & { destination: SecretSync.Render }>();
const serviceName = watch("destinationConfig.serviceName");
@@ -35,7 +35,7 @@ import { HumanitecSyncReviewFields } from "./HumanitecSyncReviewFields";
import { OCIVaultSyncReviewFields } from "./OCIVaultSyncReviewFields";
import { OnePassSyncReviewFields } from "./OnePassSyncReviewFields";
import { RailwaySyncReviewFields } from "./RailwaySyncReviewFields";
import { RenderSyncReviewFields } from "./RenderSyncReviewFields";
import { RenderSyncOptionsReviewFields, RenderSyncReviewFields } from "./RenderSyncReviewFields";
import { SupabaseSyncReviewFields } from "./SupabaseSyncReviewFields";
import { TeamCitySyncReviewFields } from "./TeamCitySyncReviewFields";
import { TerraformCloudSyncReviewFields } from "./TerraformCloudSyncReviewFields";
@@ -121,6 +121,7 @@ export const SecretSyncReviewFields = () => {
break;
case SecretSync.Render:
DestinationFieldsComponent = <RenderSyncReviewFields />;
AdditionalSyncOptionsFieldsComponent = <RenderSyncOptionsReviewFields />;
break;
case SecretSync.Flyio:
DestinationFieldsComponent = <FlyioSyncReviewFields />;
@@ -2,9 +2,13 @@ import { z } from "zod";
import { BaseSecretSyncSchema } from "@app/components/secret-syncs/forms/schemas/base-secret-sync-schema";
import { SecretSync } from "@app/hooks/api/secretSyncs";
import { RenderSyncScope, RenderSyncType } from "@app/hooks/api/secretSyncs/render-sync";
import { RenderSyncScope, RenderSyncType } from "@app/hooks/api/secretSyncs/types/render-sync";
export const RenderSyncDestinationSchema = BaseSecretSyncSchema().merge(
export const RenderSyncDestinationSchema = BaseSecretSyncSchema(
z.object({
autoRedeployServices: z.boolean().optional()
})
).merge(
z.object({
destination: z.literal(SecretSync.Render),
destinationConfig: z.discriminatedUnion("scope", [
@@ -0,0 +1,26 @@
import { ReactNode } from "react";
type Props = {
title?: string;
body?: ReactNode;
};
export const AccessRestrictedBanner = ({
title = "Access Restricted",
body = (
<>
Your current role doesn&#39;t provide access to this feature.
<br /> Contact your administrator to request access.
</>
)
}: Props) => {
return (
<div className="flex items-center rounded-md border border-mineshaft-500 bg-gradient-to-br from-mineshaft-900 to-mineshaft-600 px-16 py-12 text-center text-bunker-300">
<div>
<div className="text-4xl font-medium text-bunker-100">{title}</div>
<div className="-mt-1 text-sm">{body}</div>
</div>
</div>
);
};
@@ -0,0 +1 @@
export * from "./AccessRestrictedBanner";
@@ -1,5 +1,5 @@
import { forwardRef, TextareaHTMLAttributes, useCallback, useMemo, useRef, useState } from "react";
import { faCircle, faFolder, faKey } from "@fortawesome/free-solid-svg-icons";
import { faFolder, faKey, faLayerGroup } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import * as Popover from "@radix-ui/react-popover";
@@ -79,7 +79,7 @@ export const InfisicalSecretInput = forwardRef<HTMLTextAreaElement, Props>(
const { currentWorkspace } = useWorkspace();
const workspaceId = currentWorkspace?.id || "";
const [debouncedValue] = useDebounce(value, 500);
const [debouncedValue] = useDebounce(value, 100);
const [highlightedIndex, setHighlightedIndex] = useState(-1);
@@ -142,7 +142,7 @@ export const InfisicalSecretInput = forwardRef<HTMLTextAreaElement, Props>(
const suggestions = useMemo(() => {
if (!isPopupOpen) return [];
// reset highlight whenever recomputation happens
setHighlightedIndex(-1);
setHighlightedIndex(0);
const suggestionsArr: ReferenceItem[] = [];
const predicate = suggestionSource.predicate.toLowerCase();
@@ -298,17 +298,21 @@ export const InfisicalSecretInput = forwardRef<HTMLTextAreaElement, Props>(
}}
>
<div
className="h-full w-full max-w-60 flex-col items-center justify-center rounded-md text-white"
className="h-full w-full flex-col items-center justify-center rounded-md text-white"
ref={popoverContentRef}
>
{suggestions.map((item, i) => {
let entryIcon;
let subText;
if (item.type === ReferenceType.SECRET) {
entryIcon = faKey;
entryIcon = <FontAwesomeIcon icon={faKey} className="text-bunker-300" />;
subText = "Secret";
} else if (item.type === ReferenceType.ENVIRONMENT) {
entryIcon = faCircle;
entryIcon = <FontAwesomeIcon icon={faLayerGroup} className="text-green-700" />;
subText = "Environment";
} else {
entryIcon = faFolder;
entryIcon = <FontAwesomeIcon icon={faFolder} className="text-yellow-700" />;
subText = "Folder";
}
return (
@@ -327,22 +331,22 @@ export const InfisicalSecretInput = forwardRef<HTMLTextAreaElement, Props>(
}}
onMouseEnter={() => setHighlightedIndex(i)}
style={{ pointerEvents: "auto" }}
className="flex items-center justify-between border-mineshaft-600 text-left"
className="flex w-full items-center justify-between border-mineshaft-600 text-left"
key={`secret-reference-secret-${i + 1}`}
>
<div
className={`${
highlightedIndex === i ? "bg-gray-600" : ""
} text-md relative mb-0.5 flex w-full cursor-pointer select-none items-center justify-between rounded-md px-2 py-2 outline-none transition-all hover:bg-mineshaft-500 data-[highlighted]:bg-mineshaft-500`}
highlightedIndex === i ? "bg-mineshaft-500" : ""
} text-md relative flex w-full cursor-pointer select-none items-center justify-between px-2 py-2 outline-none transition-all hover:bg-mineshaft-700 data-[highlighted]:bg-mineshaft-700`}
>
<div className="flex w-full gap-2">
<div className="flex items-center text-yellow-700">
<FontAwesomeIcon
icon={entryIcon}
size={item.type === ReferenceType.ENVIRONMENT ? "xs" : "1x"}
/>
<div className="flex w-full items-start gap-2">
<div className="mt-1 flex items-center">{entryIcon}</div>
<div className="text-md w-10/12 truncate text-left">
{item.label}
<div className="mb-[0.1rem] text-xs leading-3 text-bunker-400">
{subText}
</div>
</div>
<div className="text-md w-10/12 truncate text-left">{item.label}</div>
</div>
</div>
</div>
@@ -20,6 +20,7 @@ type Props = {
isMulti?: boolean;
iconClassName?: string;
dropdownContainerStyle?: React.CSSProperties;
side?: SelectPrimitive.SelectContentProps["side"];
};
export type SelectProps = Omit<SelectPrimitive.SelectProps, "disabled"> & Props;
@@ -37,6 +38,7 @@ export const Select = forwardRef<HTMLButtonElement, SelectProps>(
containerClassName,
iconClassName,
dropdownContainerStyle,
side,
...props
},
ref
@@ -78,6 +80,7 @@ export const Select = forwardRef<HTMLButtonElement, SelectProps>(
</SelectPrimitive.Trigger>
<SelectPrimitive.Portal>
<SelectPrimitive.Content
side={side}
className={twMerge(
"relative top-1 z-[100] max-w-sm overflow-hidden rounded-md border border-mineshaft-600 bg-mineshaft-900 font-inter text-bunker-100 shadow-md",
position === "popper" && "max-h-72",
+1
View File
@@ -1,4 +1,5 @@
/* eslint-disable react-refresh/only-export-components */
export * from "./AccessRestrictedBanner";
export * from "./Accordion";
export * from "./Alert";
export * from "./Badge";
+2 -1
View File
@@ -171,7 +171,8 @@ export const getAppConnectionMethodDetails = (method: TAppConnection["method"])
case RenderConnectionMethod.ApiKey:
case ChecklyConnectionMethod.ApiKey:
return { name: "API Key", icon: faKey };
case AzureClientSecretsConnectionMethod.ClientSecret:
return { name: "Client Secret", icon: faKey };
default:
throw new Error(`Unhandled App Connection Method: ${method}`);
}
+1 -1
View File
@@ -4,9 +4,9 @@ import {
SecretSyncImportBehavior,
SecretSyncInitialSyncBehavior
} from "@app/hooks/api/secretSyncs";
import { RenderSyncScope } from "@app/hooks/api/secretSyncs/render-sync";
import { GcpSyncScope } from "@app/hooks/api/secretSyncs/types/gcp-sync";
import { HumanitecSyncScope } from "@app/hooks/api/secretSyncs/types/humanitec-sync";
import { RenderSyncScope } from "@app/hooks/api/secretSyncs/types/render-sync";
export const SECRET_SYNC_MAP: Record<SecretSync, { name: string; image: string }> = {
[SecretSync.AWSParameterStore]: { name: "AWS Parameter Store", image: "Amazon Web Services.png" },
@@ -1,9 +1,8 @@
import { ComponentType } from "react";
import { Abilities, AbilityTuple, Generics, SubjectType } from "@casl/ability";
import { faLock } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { twMerge } from "tailwind-merge";
import { AccessRestrictedBanner } from "@app/components/v2";
import { TOrgPermission, useOrgPermission } from "@app/context";
type Props<T extends Abilities> = (T extends AbilityTuple
@@ -14,11 +13,11 @@ type Props<T extends Abilities> = (T extends AbilityTuple
: {
action: string;
subject: string;
}) & { className?: string; containerClassName?: string };
}) & { containerClassName?: string };
export const withPermission = <T extends object, J extends TOrgPermission>(
Component: ComponentType<T>,
{ action, subject, className, containerClassName }: Props<Generics<J>["abilities"]>
{ action, subject, containerClassName }: Props<Generics<J>["abilities"]>
) => {
const HOC = (hocProps: T) => {
const { permission } = useOrgPermission();
@@ -33,22 +32,7 @@ export const withPermission = <T extends object, J extends TOrgPermission>(
containerClassName
)}
>
<div
className={twMerge(
"flex items-end space-x-12 rounded-md bg-mineshaft-800 p-16 text-bunker-300",
className
)}
>
<div>
<FontAwesomeIcon icon={faLock} size="6x" />
</div>
<div>
<div className="mb-2 text-4xl font-medium">Access Restricted</div>
<div className="text-sm">
Your role has limited permissions, please <br /> contact your admin to gain access
</div>
</div>
</div>
<AccessRestrictedBanner />
</div>
);
}
@@ -1,14 +1,12 @@
import { ComponentType } from "react";
import { AbilityTuple } from "@casl/ability";
import { faLock } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { twMerge } from "tailwind-merge";
import { AccessRestrictedBanner } from "@app/components/v2";
import { useProjectPermission } from "@app/context";
import { ProjectPermissionSet } from "@app/context/ProjectPermissionContext";
type Props<T extends AbilityTuple> = {
className?: string;
containerClassName?: string;
action: T[0];
subject: T[1];
@@ -16,7 +14,7 @@ type Props<T extends AbilityTuple> = {
export const withProjectPermission = <T extends object>(
Component: ComponentType<Omit<Props<ProjectPermissionSet>, "action" | "subject"> & T>,
{ action, subject, className, containerClassName }: Props<ProjectPermissionSet>
{ action, subject, containerClassName }: Props<ProjectPermissionSet>
) => {
const HOC = (hocProps: Omit<Props<ProjectPermissionSet>, "action" | "subject"> & T) => {
const { permission } = useProjectPermission();
@@ -31,23 +29,7 @@ export const withProjectPermission = <T extends object>(
containerClassName
)}
>
<div
className={twMerge(
"flex items-end space-x-12 rounded-md bg-mineshaft-800 p-16 text-bunker-300",
className
)}
>
<div>
<FontAwesomeIcon icon={faLock} size="6x" />
</div>
<div>
<div className="mb-2 text-4xl font-medium">Permission Denied</div>
<div className="text-sm">
You do not have permission to this page. <br /> Kindly contact your organization
administrator
</div>
</div>
</div>
<AccessRestrictedBanner />
</div>
);
}
@@ -17,7 +17,7 @@ export const useCreateAccessApprovalPolicy = () => {
return useMutation<object, object, TCreateAccessPolicyDTO>({
mutationFn: async ({
environment,
environments,
projectSlug,
approvals,
approvers,
@@ -29,7 +29,7 @@ export const useCreateAccessApprovalPolicy = () => {
approvalsRequired
}) => {
const { data } = await apiRequest.post("/api/v1/access-approvals/policies", {
environment,
environments,
projectSlug,
approvals,
bypassers,
@@ -63,7 +63,8 @@ export const useUpdateAccessApprovalPolicy = () => {
secretPath,
enforcementLevel,
allowedSelfApprovals,
approvalsRequired
approvalsRequired,
environments
}) => {
const { data } = await apiRequest.patch(`/api/v1/access-approvals/policies/${id}`, {
approvals,
@@ -73,7 +74,8 @@ export const useUpdateAccessApprovalPolicy = () => {
name,
enforcementLevel,
allowedSelfApprovals,
approvalsRequired
approvalsRequired,
environments
});
return data;
},
@@ -8,9 +8,8 @@ export type TAccessApprovalPolicy = {
name: string;
approvals: number;
secretPath: string;
envId: string;
workspace: string;
environment: WorkspaceEnv;
environments: WorkspaceEnv[];
projectId: string;
policyType: PolicyType;
approversRequired: boolean;
@@ -166,7 +165,7 @@ export type TGetSecretApprovalPolicyOfBoardDTO = {
export type TCreateAccessPolicyDTO = {
projectSlug: string;
name?: string;
environment: string;
environments: string[];
approvers?: Approver[];
bypassers?: Bypasser[];
approvals?: number;
@@ -182,7 +181,7 @@ export type TUpdateAccessPolicyDTO = {
approvers?: Approver[];
bypassers?: Bypasser[];
secretPath?: string;
environment?: string;
environments?: string[];
approvals?: number;
enforcementLevel?: EnforcementLevel;
allowedSelfApprovals: boolean;
+1
View File
@@ -51,6 +51,7 @@ export type TServerConfig = {
invalidatingCache: boolean;
fipsEnabled: boolean;
envOverrides?: Record<string, string>;
paramsFolderSecretDetectionEnabled: boolean;
};
export type TUpdateServerConfigDTO = {
@@ -2,15 +2,26 @@ import { AppConnection } from "@app/hooks/api/appConnections/enums";
import { TRootAppConnection } from "@app/hooks/api/appConnections/types/root-connection";
export enum AzureClientSecretsConnectionMethod {
OAuth = "oauth"
OAuth = "oauth",
ClientSecret = "client-secret"
}
export type TAzureClientSecretsConnection = TRootAppConnection & {
app: AppConnection.AzureClientSecrets;
} & {
method: AzureClientSecretsConnectionMethod.OAuth;
credentials: {
code: string;
tenantId: string;
};
};
} & (
| {
method: AzureClientSecretsConnectionMethod.OAuth;
credentials: {
code: string;
tenantId: string;
};
}
| {
method: AzureClientSecretsConnectionMethod.ClientSecret;
credentials: {
clientSecret: string;
clientId: string;
tenantId: string;
};
}
);
@@ -11,6 +11,7 @@ export type TGitHubConnection = TRootAppConnection & { app: AppConnection.GitHub
method: GitHubConnectionMethod.OAuth;
credentials: {
code: string;
host?: string;
};
}
| {
@@ -18,6 +19,7 @@ export type TGitHubConnection = TRootAppConnection & { app: AppConnection.GitHub
credentials: {
code: string;
installationId: string;
host?: string;
};
}
);
@@ -86,6 +86,7 @@ export const useCreateIdentity = () => {
queryClient.invalidateQueries({
queryKey: subscriptionQueryKeys.getOrgSubsription(organizationId)
});
queryClient.invalidateQueries({ queryKey: identitiesKeys.searchIdentitiesRoot });
}
});
};
@@ -110,6 +111,7 @@ export const useUpdateIdentity = () => {
queryKey: organizationKeys.getOrgIdentityMemberships(organizationId)
});
queryClient.invalidateQueries({ queryKey: identitiesKeys.getIdentityById(identityId) });
queryClient.invalidateQueries({ queryKey: identitiesKeys.searchIdentitiesRoot });
}
});
};
@@ -130,6 +132,7 @@ export const useDeleteIdentity = () => {
queryClient.invalidateQueries({
queryKey: subscriptionQueryKeys.getOrgSubsription(organizationId)
});
queryClient.invalidateQueries({ queryKey: identitiesKeys.searchIdentitiesRoot });
}
});
};
@@ -25,7 +25,9 @@ import {
export const identitiesKeys = {
getIdentityById: (identityId: string) => [{ identityId }, "identity"] as const,
searchIdentities: (dto: TSearchIdentitiesDTO) => ["identity", "search", dto] as const,
searchIdentitiesRoot: ["identity", "search"] as const,
searchIdentities: (dto: TSearchIdentitiesDTO) =>
[...identitiesKeys.searchIdentitiesRoot, dto] as const,
getIdentityUniversalAuth: (identityId: string) =>
[{ identityId }, "identity-universal-auth"] as const,
getIdentityUniversalAuthClientSecrets: (identityId: string) =>
@@ -0,0 +1 @@
export * from "./mutations";
+24 -1
View File
@@ -15,7 +15,7 @@ export const useImportEnvKey = () => {
formData.append("file", file);
try {
const response = await apiRequest.post("/api/v3/migrate/env-key/", formData, {
const response = await apiRequest.post("/api/v3/external-migration/env-key/", formData, {
headers: {
"Content-Type": "multipart/form-data"
},
@@ -39,3 +39,26 @@ export const useImportEnvKey = () => {
}
});
};
export const useImportVault = () => {
return useMutation({
mutationFn: async ({
vaultAccessToken,
vaultNamespace,
vaultUrl,
mappingType
}: {
vaultAccessToken: string;
vaultNamespace?: string;
vaultUrl: string;
mappingType: string;
}) => {
await apiRequest.post("/api/v3/external-migration/vault/", {
vaultAccessToken,
vaultNamespace,
vaultUrl,
mappingType
});
}
});
};
@@ -10,7 +10,7 @@ export const useCreateSecretApprovalPolicy = () => {
return useMutation<object, object, TCreateSecretPolicyDTO>({
mutationFn: async ({
environment,
environments,
workspaceId,
approvals,
approvers,
@@ -21,7 +21,7 @@ export const useCreateSecretApprovalPolicy = () => {
allowedSelfApprovals
}) => {
const { data } = await apiRequest.post("/api/v1/secret-approvals", {
environment,
environments,
workspaceId,
approvals,
approvers,
@@ -53,7 +53,8 @@ export const useUpdateSecretApprovalPolicy = () => {
secretPath,
name,
enforcementLevel,
allowedSelfApprovals
allowedSelfApprovals,
environments
}) => {
const { data } = await apiRequest.patch(`/api/v1/secret-approvals/${id}`, {
approvals,
@@ -62,7 +63,8 @@ export const useUpdateSecretApprovalPolicy = () => {
secretPath,
name,
enforcementLevel,
allowedSelfApprovals
allowedSelfApprovals,
environments
});
return data;
},
@@ -5,8 +5,7 @@ export type TSecretApprovalPolicy = {
id: string;
workspace: string;
name: string;
envId: string;
environment: WorkspaceEnv;
environments: WorkspaceEnv[];
secretPath?: string;
approvals: number;
approvers: Approver[];
@@ -48,7 +47,7 @@ export type TGetSecretApprovalPolicyOfBoardDTO = {
export type TCreateSecretPolicyDTO = {
workspaceId: string;
name?: string;
environment: string;
environments: string[];
secretPath: string;
approvers?: Approver[];
bypassers?: Bypasser[];
@@ -68,6 +67,7 @@ export type TUpdateSecretPolicyDTO = {
enforcementLevel?: EnforcementLevel;
// for invalidating list
workspaceId: string;
environments?: string[];
};
export type TDeleteSecretPolicyDTO = {
@@ -1,7 +1,6 @@
import { SecretSync, SecretSyncImportBehavior } from "@app/hooks/api/secretSyncs";
import { DiscriminativePick } from "@app/types";
import { TRenderSync } from "../render-sync";
import { TOnePassSync } from "./1password-sync";
import { TAwsParameterStoreSync } from "./aws-parameter-store-sync";
import { TAwsSecretsManagerSync } from "./aws-secrets-manager-sync";
@@ -24,6 +23,7 @@ import { THerokuSync } from "./heroku-sync";
import { THumanitecSync } from "./humanitec-sync";
import { TOCIVaultSync } from "./oci-vault-sync";
import { TRailwaySync } from "./railway-sync";
import { TRenderSync } from "./render-sync";
import { TSupabaseSync } from "./supabase";
import { TTeamCitySync } from "./teamcity-sync";
import { TTerraformCloudSync } from "./terraform-cloud-sync";
@@ -1,6 +1,6 @@
import { AppConnection } from "@app/hooks/api/appConnections/enums";
import { SecretSync } from "@app/hooks/api/secretSyncs";
import { TRootSecretSync } from "@app/hooks/api/secretSyncs/types/root-sync";
import { RootSyncOptions, TRootSecretSync } from "@app/hooks/api/secretSyncs/types/root-sync";
export type TRenderSync = TRootSecretSync & {
destination: SecretSync.Render;
@@ -16,6 +16,10 @@ export type TRenderSync = TRootSecretSync & {
name: string;
id: string;
};
syncOptions: RootSyncOptions & {
autoRedeployServices?: boolean;
};
};
export enum RenderSyncScope {
+4 -2
View File
@@ -281,7 +281,8 @@ export const useUpdateProject = () => {
newProjectDescription,
newSlug,
secretSharing,
showSnapshotsLegacy
showSnapshotsLegacy,
secretDetectionIgnoreValues
}) => {
const { data } = await apiRequest.patch<{ workspace: Workspace }>(
`/api/v1/workspace/${projectID}`,
@@ -290,7 +291,8 @@ export const useUpdateProject = () => {
description: newProjectDescription,
slug: newSlug,
secretSharing,
showSnapshotsLegacy
showSnapshotsLegacy,
secretDetectionIgnoreValues
}
);
return data.workspace;
@@ -40,6 +40,7 @@ export type Workspace = {
hasDeleteProtection: boolean;
secretSharing: boolean;
showSnapshotsLegacy: boolean;
secretDetectionIgnoreValues: string[];
};
export type WorkspaceEnv = {
@@ -81,6 +82,7 @@ export type UpdateProjectDTO = {
newSlug?: string;
secretSharing?: boolean;
showSnapshotsLegacy?: boolean;
secretDetectionIgnoreValues?: string[];
};
export type UpdatePitVersionLimitDTO = { projectSlug: string; pitVersionLimit: number };
+2 -1
View File
@@ -49,7 +49,8 @@ export const usePathAccessPolicies = ({ secretPath, environment }: Params) => {
return useMemo(() => {
const pathPolicies = policies?.filter(
(policy) =>
policy.environment.slug === environment && matchesPath(secretPath, policy.secretPath)
policy.environments?.some((env) => env.slug === environment) &&
matchesPath(secretPath, policy.secretPath)
);
return {
@@ -0,0 +1,71 @@
import { MouseEvent, useCallback, useEffect, useRef, useState } from "react";
type Params = {
minWidth: number;
maxWidth: number;
initialWidth: number;
};
export const useResizableColWidth = ({ minWidth, maxWidth, initialWidth }: Params) => {
const [colWidth, setColWidth] = useState(initialWidth);
const [isResizing, setIsResizing] = useState(false);
const startX = useRef(0);
const startWidth = useRef(0);
const handleMouseDown = useCallback(
(e: MouseEvent<HTMLDivElement>) => {
e.preventDefault();
e.stopPropagation();
setIsResizing(true);
startX.current = e.clientX;
startWidth.current = colWidth;
},
[colWidth]
);
const handleMouseMove = useCallback(
(e: MouseEvent) => {
if (!isResizing) return;
const deltaX = e.clientX - startX.current;
const newWidth = Math.max(minWidth, Math.min(maxWidth, startWidth.current + deltaX));
setColWidth(newWidth);
},
[isResizing]
);
const handleMouseUp = useCallback(() => {
setIsResizing(false);
}, []);
useEffect(() => {
if (isResizing) {
document.addEventListener(
"mousemove",
// @ts-expect-error native discrepancy
handleMouseMove
);
document.addEventListener("mouseup", handleMouseUp);
document.body.style.cursor = "ew-resize";
document.body.style.userSelect = "none";
}
return () => {
document.removeEventListener(
"mousemove",
// @ts-expect-error native discrepancy
handleMouseMove
);
document.removeEventListener("mouseup", handleMouseUp);
document.body.style.cursor = "";
document.body.style.userSelect = "";
};
}, [isResizing, handleMouseMove, handleMouseUp]);
return {
colWidth,
handleMouseDown,
isResizing
};
};
+18 -1
View File
@@ -1,4 +1,4 @@
import { faCog, faCube, faHome, faLock, faUsers } from "@fortawesome/free-solid-svg-icons";
import { faBook, faCog, faCube, faHome, faLock, faUsers } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { Link, Outlet } from "@tanstack/react-router";
import { motion } from "framer-motion";
@@ -84,6 +84,23 @@ export const KmsLayout = () => {
</MenuItem>
)}
</Link>
<Link
to="/projects/kms/$projectId/audit-logs"
params={{
projectId: currentWorkspace.id
}}
>
{({ isActive }) => (
<MenuItem isSelected={isActive}>
<div className="mx-1 flex gap-2">
<div className="w-6">
<FontAwesomeIcon icon={faBook} />
</div>
Audit Logs
</div>
</MenuItem>
)}
</Link>
<Link
to="/projects/kms/$projectId/settings"
params={{
@@ -1,6 +1,7 @@
import { useTranslation } from "react-i18next";
import {
faBell,
faBook,
faCertificate,
faCog,
faFileLines,
@@ -148,6 +149,23 @@ export const PkiManagerLayout = () => {
</MenuItem>
)}
</Link>
<Link
to="/projects/cert-management/$projectId/audit-logs"
params={{
projectId: currentWorkspace.id
}}
>
{({ isActive }) => (
<MenuItem isSelected={isActive}>
<div className="mx-1 flex gap-2">
<div className="w-6">
<FontAwesomeIcon icon={faBook} />
</div>
Audit Logs
</div>
</MenuItem>
)}
</Link>
<Link
to="/projects/cert-management/$projectId/settings"
params={{
@@ -1,6 +1,7 @@
import { useTranslation } from "react-i18next";
import {
faArrowsSpin,
faBook,
faCheckToSlot,
faCog,
faHome,
@@ -166,6 +167,23 @@ export const SecretManagerLayout = () => {
</MenuItem>
)}
</Link>
<Link
to="/projects/secret-management/$projectId/audit-logs"
params={{
projectId: currentWorkspace.id
}}
>
{({ isActive }) => (
<MenuItem isSelected={isActive}>
<div className="mx-1 flex gap-2">
<div className="w-6">
<FontAwesomeIcon icon={faBook} />
</div>
Audit Logs
</div>
</MenuItem>
)}
</Link>
<Link
to="/projects/secret-management/$projectId/settings"
params={{
@@ -1,4 +1,5 @@
import {
faBook,
faCog,
faDatabase,
faHome,
@@ -9,8 +10,15 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { Link, Outlet } from "@tanstack/react-router";
import { motion } from "framer-motion";
import { Lottie, Menu, MenuGroup, MenuItem } from "@app/components/v2";
import { useProjectPermission, useWorkspace } from "@app/context";
import { Badge, Lottie, Menu, MenuGroup, MenuItem } from "@app/components/v2";
import {
ProjectPermissionSub,
useProjectPermission,
useSubscription,
useWorkspace
} from "@app/context";
import { ProjectPermissionSecretScanningFindingActions } from "@app/context/ProjectPermissionContext/types";
import { useGetSecretScanningUnresolvedFindingCount } from "@app/hooks/api/secretScanningV2";
import { AssumePrivilegeModeBanner } from "../ProjectLayout/components/AssumePrivilegeModeBanner";
@@ -18,6 +26,22 @@ export const SecretScanningLayout = () => {
const { currentWorkspace } = useWorkspace();
const { assumedPrivilegeDetails } = useProjectPermission();
const { permission } = useProjectPermission();
const { subscription } = useSubscription();
const { data: unresolvedFindings } = useGetSecretScanningUnresolvedFindingCount(
currentWorkspace.id,
{
enabled:
subscription.secretScanning &&
permission.can(
ProjectPermissionSecretScanningFindingActions.Read,
ProjectPermissionSub.SecretScanningFindings
),
refetchInterval: 30000
}
);
return (
<div className="dark hidden h-full w-full flex-col overflow-x-hidden md:flex">
<div className="flex flex-grow flex-col overflow-y-hidden md:flex-row">
@@ -62,11 +86,16 @@ export const SecretScanningLayout = () => {
>
{({ isActive }) => (
<MenuItem isSelected={isActive}>
<div className="mx-1 flex gap-2">
<div className="mx-1 flex w-full gap-2">
<div className="w-6">
<FontAwesomeIcon icon={faMagnifyingGlass} />
</div>
Findings
<span>Findings</span>
{Boolean(unresolvedFindings) && (
<Badge variant="primary" className="ml-auto mr-2 h-min">
{unresolvedFindings}
</Badge>
)}
</div>
</MenuItem>
)}
@@ -90,6 +119,23 @@ export const SecretScanningLayout = () => {
</MenuItem>
)}
</Link>
<Link
to="/projects/secret-scanning/$projectId/audit-logs"
params={{
projectId: currentWorkspace.id
}}
>
{({ isActive }) => (
<MenuItem isSelected={isActive}>
<div className="mx-1 flex gap-2">
<div className="w-6">
<FontAwesomeIcon icon={faBook} />
</div>
Audit Logs
</div>
</MenuItem>
)}
</Link>
<Link
to="/projects/secret-scanning/$projectId/settings"
params={{
+25 -1
View File
@@ -1,4 +1,11 @@
import { faCog, faHome, faServer, faStamp, faUsers } from "@fortawesome/free-solid-svg-icons";
import {
faBook,
faCog,
faHome,
faServer,
faStamp,
faUsers
} from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { Link, Outlet } from "@tanstack/react-router";
import { motion } from "framer-motion";
@@ -100,6 +107,23 @@ export const SshLayout = () => {
</MenuItem>
)}
</Link>
<Link
to="/projects/ssh/$projectId/audit-logs"
params={{
projectId: currentWorkspace.id
}}
>
{({ isActive }) => (
<MenuItem isSelected={isActive}>
<div className="mx-1 flex gap-2">
<div className="w-6">
<FontAwesomeIcon icon={faBook} />
</div>
Audit Logs
</div>
</MenuItem>
)}
</Link>
<Link
to="/projects/ssh/$projectId/settings"
params={{
@@ -22,7 +22,6 @@ export const KmipPage = () => {
description="Integrate with Infisical KMS via Key Management Interoperability Protocol."
/>
<ProjectPermissionCan
passThrough={false}
renderGuardBanner
I={ProjectPermissionKmipActions.ReadClients}
a={ProjectPermissionSub.Kmip}
@@ -22,7 +22,6 @@ export const OverviewPage = () => {
description="Manage keys and perform cryptographic operations."
/>
<ProjectPermissionCan
passThrough={false}
renderGuardBanner
I={ProjectPermissionActions.Read}
a={ProjectPermissionSub.Cmek}
@@ -114,7 +114,7 @@ const CreateForm = ({ app, onComplete }: CreateFormProps) => {
case AppConnection.Camunda:
return <CamundaConnectionForm onSubmit={onSubmit} />;
case AppConnection.AzureClientSecrets:
return <AzureClientSecretsConnectionForm />;
return <AzureClientSecretsConnectionForm onSubmit={onSubmit} />;
case AppConnection.AzureDevOps:
return <AzureDevOpsConnectionForm onSubmit={onSubmit} />;
case AppConnection.Windmill:
@@ -222,7 +222,7 @@ const UpdateForm = ({ appConnection, onComplete }: UpdateFormProps) => {
case AppConnection.Camunda:
return <CamundaConnectionForm onSubmit={onSubmit} appConnection={appConnection} />;
case AppConnection.AzureClientSecrets:
return <AzureClientSecretsConnectionForm appConnection={appConnection} />;
return <AzureClientSecretsConnectionForm appConnection={appConnection} onSubmit={onSubmit} />;
case AppConnection.AzureDevOps:
return <AzureDevOpsConnectionForm appConnection={appConnection} onSubmit={onSubmit} />;
case AppConnection.Windmill:
@@ -1,3 +1,4 @@
/* eslint-disable no-case-declarations */
import crypto from "crypto";
import { useState } from "react";
@@ -20,19 +21,83 @@ import {
GenericAppConnectionsFields
} from "./GenericAppConnectionFields";
type ClientSecretForm = z.infer<typeof clientSecretSchema>;
type Props = {
appConnection?: TAzureClientSecretsConnection;
onSubmit: (formData: ClientSecretForm) => Promise<void>;
};
const formSchema = genericAppConnectionFieldsSchema.extend({
const baseSchema = genericAppConnectionFieldsSchema.extend({
app: z.literal(AppConnection.AzureClientSecrets),
method: z.nativeEnum(AzureClientSecretsConnectionMethod),
tenantId: z.string().trim().min(1, "Tenant ID is required")
method: z.nativeEnum(AzureClientSecretsConnectionMethod)
});
const oauthSchema = baseSchema.extend({
tenantId: z.string().trim().min(1, "Tenant ID is required"),
method: z.literal(AzureClientSecretsConnectionMethod.OAuth)
});
const clientSecretSchema = baseSchema.extend({
method: z.literal(AzureClientSecretsConnectionMethod.ClientSecret),
credentials: z.object({
clientSecret: z.string().trim().min(1, "Client Secret is required"),
clientId: z.string().trim().min(1, "Client ID is required"),
tenantId: z.string().trim().min(1, "Tenant ID is required")
})
});
const formSchema = z.discriminatedUnion("method", [oauthSchema, clientSecretSchema]);
type FormData = z.infer<typeof formSchema>;
export const AzureClientSecretsConnectionForm = ({ appConnection }: Props) => {
const getDefaultValues = (appConnection?: TAzureClientSecretsConnection): Partial<FormData> => {
if (!appConnection) {
return {
app: AppConnection.AzureClientSecrets,
method: AzureClientSecretsConnectionMethod.OAuth
};
}
const base = {
name: appConnection.name,
description: appConnection.description,
app: appConnection.app,
method: appConnection.method
};
const { credentials } = appConnection;
switch (appConnection.method) {
case AzureClientSecretsConnectionMethod.OAuth:
if ("tenantId" in credentials) {
return {
...base,
method: AzureClientSecretsConnectionMethod.OAuth,
tenantId: credentials.tenantId
};
}
break;
case AzureClientSecretsConnectionMethod.ClientSecret:
if ("clientSecret" in credentials && "clientId" in credentials) {
return {
...base,
method: AzureClientSecretsConnectionMethod.ClientSecret,
credentials: {
clientSecret: credentials.clientSecret,
clientId: credentials.clientId,
tenantId: credentials.tenantId
}
};
}
break;
default:
return base;
}
return base;
};
export const AzureClientSecretsConnectionForm = ({ appConnection, onSubmit }: Props) => {
const isUpdate = Boolean(appConnection);
const [isRedirecting, setIsRedirecting] = useState(false);
@@ -43,70 +108,51 @@ export const AzureClientSecretsConnectionForm = ({ appConnection }: Props) => {
const form = useForm<FormData>({
resolver: zodResolver(formSchema),
defaultValues: appConnection
? {
...appConnection,
tenantId: appConnection.credentials.tenantId
}
: {
app: AppConnection.AzureClientSecrets,
method: AzureClientSecretsConnectionMethod.OAuth
}
defaultValues: getDefaultValues(appConnection)
});
const {
handleSubmit,
control,
watch,
setValue,
formState: { isSubmitting, isDirty }
} = form;
const selectedMethod = watch("method");
const onSubmit = (formData: FormData) => {
setIsRedirecting(true);
const onSubmitHandler = (formData: FormData) => {
const state = crypto.randomBytes(16).toString("hex");
localStorage.setItem("latestCSRFToken", state);
localStorage.setItem(
"azureClientSecretsConnectionFormData",
JSON.stringify({ ...formData, connectionId: appConnection?.id })
);
switch (formData.method) {
case AzureClientSecretsConnectionMethod.OAuth:
window.location.assign(
`https://login.microsoftonline.com/${formData.tenantId || "common"}/oauth2/v2.0/authorize?client_id=${oauthClientId}&response_type=code&redirect_uri=${window.location.origin}/organization/app-connections/azure/oauth/callback&response_mode=query&scope=https://azconfig.io/.default%20openid%20offline_access&state=${state}<:>azure-client-secrets`
setIsRedirecting(true);
localStorage.setItem("latestCSRFToken", state);
localStorage.setItem(
"azureClientSecretsConnectionFormData",
JSON.stringify({ ...formData, connectionId: appConnection?.id })
);
window.location.assign(
`https://login.microsoftonline.com/${formData.tenantId || "common"}/oauth2/v2.0/authorize?client_id=${oauthClientId}&response_type=code&redirect_uri=${window.location.origin}/organization/app-connections/azure/oauth/callback&response_mode=query&scope=https://graph.microsoft.com/.default%20openid%20offline_access&state=${state}<:>azure-client-secrets`
);
break;
case AzureClientSecretsConnectionMethod.ClientSecret:
onSubmit(formData);
break;
default:
throw new Error(`Unhandled Azure Connection method: ${(formData as FormData).method}`);
}
};
const isMissingConfig = !oauthClientId;
const isMissingConfig =
selectedMethod === AzureClientSecretsConnectionMethod.OAuth && !oauthClientId;
const methodDetails = getAppConnectionMethodDetails(selectedMethod);
return (
<FormProvider {...form}>
<form onSubmit={handleSubmit(onSubmit)}>
<form onSubmit={handleSubmit(onSubmitHandler)}>
{!isUpdate && <GenericAppConnectionsFields />}
<Controller
name="tenantId"
control={control}
render={({ field, fieldState: { error } }) => (
<FormControl
tooltipText="The Directory (tenant) ID."
isError={Boolean(error?.message)}
label="Tenant ID"
errorText={error?.message}
>
<Input {...field} placeholder="e4f34ea5-ad23-4291-8585-66d20d603cc8" />
</FormControl>
)}
/>
<Controller
name="method"
control={control}
@@ -146,6 +192,61 @@ export const AzureClientSecretsConnectionForm = ({ appConnection }: Props) => {
</FormControl>
)}
/>
<Controller
name="tenantId"
control={control}
render={({ field, fieldState: { error } }) => (
<FormControl
tooltipText="The Directory (tenant) ID."
isError={Boolean(error?.message)}
label="Tenant ID"
errorText={error?.message}
>
<Input
{...field}
placeholder="00000000-0000-0000-0000-000000000000"
onChange={(e) => {
field.onChange(e.target.value);
setValue("credentials.tenantId", e.target.value);
}}
/>
</FormControl>
)}
/>
{/* Access Token-specific fields */}
{selectedMethod === AzureClientSecretsConnectionMethod.ClientSecret && (
<>
<Controller
name="credentials.clientId"
control={control}
render={({ field, fieldState: { error } }) => (
<FormControl
isError={Boolean(error?.message)}
label="Client ID"
errorText={error?.message}
>
<Input {...field} placeholder="00000000-0000-0000-0000-000000000000" />
</FormControl>
)}
/>
<Controller
name="credentials.clientSecret"
control={control}
render={({ field, fieldState: { error } }) => (
<FormControl
isError={Boolean(error?.message)}
label="Client Secret"
errorText={error?.message}
>
<Input {...field} type="password" placeholder="Enter your Client Secret" />
</FormControl>
)}
/>
</>
)}
<div className="mt-8 flex items-center">
<Button
className="mr-4"
@@ -3,11 +3,31 @@ import crypto from "crypto";
import { useState } from "react";
import { Controller, FormProvider, useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { useQuery } from "@tanstack/react-query";
import { z } from "zod";
import { Button, FormControl, ModalClose, Select, SelectItem } from "@app/components/v2";
import { OrgPermissionCan } from "@app/components/permissions";
import {
Accordion,
AccordionContent,
AccordionItem,
AccordionTrigger,
Button,
FormControl,
Input,
ModalClose,
Select,
SelectItem,
Tooltip
} from "@app/components/v2";
import { useSubscription } from "@app/context";
import {
OrgGatewayPermissionActions,
OrgPermissionSubjects
} from "@app/context/OrgPermissionContext/types";
import { APP_CONNECTION_MAP, getAppConnectionMethodDetails } from "@app/helpers/appConnections";
import { isInfisicalCloud } from "@app/helpers/platform";
import { gatewaysQueryKeys } from "@app/hooks/api";
import {
GitHubConnectionMethod,
TGitHubConnection,
@@ -26,7 +46,12 @@ type Props = {
const formSchema = genericAppConnectionFieldsSchema.extend({
app: z.literal(AppConnection.GitHub),
method: z.nativeEnum(GitHubConnectionMethod)
method: z.nativeEnum(GitHubConnectionMethod),
credentials: z
.object({
host: z.string().optional()
})
.optional()
});
type FormData = z.infer<typeof formSchema>;
@@ -44,7 +69,8 @@ export const GitHubConnectionForm = ({ appConnection }: Props) => {
resolver: zodResolver(formSchema),
defaultValues: appConnection ?? {
app: AppConnection.GitHub,
method: GitHubConnectionMethod.App
method: GitHubConnectionMethod.App,
gatewayId: null
}
});
@@ -55,6 +81,9 @@ export const GitHubConnectionForm = ({ appConnection }: Props) => {
formState: { isSubmitting, isDirty }
} = form;
const { subscription } = useSubscription();
const { data: gateways, isPending: isGatewaysLoading } = useQuery(gatewaysQueryKeys.list());
const selectedMethod = watch("method");
const onSubmit = (formData: FormData) => {
@@ -66,15 +95,20 @@ export const GitHubConnectionForm = ({ appConnection }: Props) => {
JSON.stringify({ ...formData, connectionId: appConnection?.id })
);
const githubHost =
formData.credentials?.host && formData.credentials.host.length > 0
? `https://${formData.credentials.host}`
: "https://github.com";
switch (formData.method) {
case GitHubConnectionMethod.App:
window.location.assign(
`https://github.com/apps/${appClientSlug}/installations/new?state=${state}`
`${githubHost}/apps/${appClientSlug}/installations/new?state=${state}`
);
break;
case GitHubConnectionMethod.OAuth:
window.location.assign(
`https://github.com/login/oauth/authorize?client_id=${oauthClientId}&response_type=code&scope=repo,admin:org&redirect_uri=${window.location.origin}/organization/app-connections/github/oauth/callback&state=${state}`
`${githubHost}/login/oauth/authorize?client_id=${oauthClientId}&response_type=code&scope=repo,admin:org&redirect_uri=${window.location.origin}/organization/app-connections/github/oauth/callback&state=${state}`
);
break;
default:
@@ -141,6 +175,81 @@ export const GitHubConnectionForm = ({ appConnection }: Props) => {
</FormControl>
)}
/>
{subscription.gateway && (
<Accordion type="single" collapsible className="w-full">
<AccordionItem value="enterprise-options" className="data-[state=open]:border-none">
<AccordionTrigger className="h-fit flex-none pl-1 text-sm">
<div className="order-1 ml-3">GitHub Enterprise Options</div>
</AccordionTrigger>
<AccordionContent childrenClassName="px-0">
<OrgPermissionCan
I={OrgGatewayPermissionActions.AttachGateways}
a={OrgPermissionSubjects.Gateway}
>
{(isAllowed) => (
<Controller
control={control}
name="gatewayId"
defaultValue=""
render={({ field: { value, onChange }, fieldState: { error } }) => (
<FormControl
isError={Boolean(error?.message)}
errorText={error?.message}
label="Gateway"
>
<Tooltip
isDisabled={isAllowed}
content="Restricted access. You don't have permission to attach gateways to resources."
>
<div>
<Select
isDisabled={!isAllowed}
value={value as string}
onValueChange={onChange}
className="w-full border border-mineshaft-500"
dropdownContainerClassName="max-w-none"
isLoading={isGatewaysLoading}
placeholder="Default: Internet Gateway"
position="popper"
>
<SelectItem
value={null as unknown as string}
onClick={() => onChange(undefined)}
>
Internet Gateway
</SelectItem>
{gateways?.map((el) => (
<SelectItem value={el.id} key={el.id}>
{el.name}
</SelectItem>
))}
</Select>
</div>
</Tooltip>
</FormControl>
)}
/>
)}
</OrgPermissionCan>
<Controller
name="credentials.host"
control={control}
shouldUnregister
render={({ field, fieldState: { error } }) => (
<FormControl
errorText={error?.message}
isError={Boolean(error?.message)}
label="Hostname"
isOptional
>
<Input {...field} placeholder="github.com" />
</FormControl>
)}
/>
</AccordionContent>
</AccordionItem>
</Accordion>
)}
<div className="mt-8 flex items-center">
<Button
className="mr-4"
@@ -6,6 +6,7 @@ import { z } from "zod";
import { OrgPermissionCan } from "@app/components/permissions";
import { Button, FormControl, ModalClose, Select, SelectItem, Tooltip } from "@app/components/v2";
import { useSubscription } from "@app/context";
import {
OrgGatewayPermissionActions,
OrgPermissionSubjects
@@ -78,6 +79,7 @@ export const MsSqlConnectionForm = ({ appConnection, onSubmit }: Props) => {
formState: { isSubmitting, isDirty }
} = form;
const { subscription } = useSubscription();
const isPlatformManagedCredentials = appConnection?.isPlatformManagedCredentials ?? false;
const { data: gateways, isPending: isGatewaysLoading } = useQuery(gatewaysQueryKeys.list());
@@ -99,55 +101,57 @@ export const MsSqlConnectionForm = ({ appConnection, onSubmit }: Props) => {
}}
>
{!isUpdate && <GenericAppConnectionsFields />}
<OrgPermissionCan
I={OrgGatewayPermissionActions.AttachGateways}
a={OrgPermissionSubjects.Gateway}
>
{(isAllowed) => (
<Controller
control={control}
name="gatewayId"
defaultValue=""
render={({ field: { value, onChange }, fieldState: { error } }) => (
<FormControl
isError={Boolean(error?.message)}
errorText={error?.message}
label="Gateway"
>
<Tooltip
isDisabled={isAllowed}
content="Restricted access. You don't have permission to attach gateways to resources."
{subscription.gateway && (
<OrgPermissionCan
I={OrgGatewayPermissionActions.AttachGateways}
a={OrgPermissionSubjects.Gateway}
>
{(isAllowed) => (
<Controller
control={control}
name="gatewayId"
defaultValue=""
render={({ field: { value, onChange }, fieldState: { error } }) => (
<FormControl
isError={Boolean(error?.message)}
errorText={error?.message}
label="Gateway"
>
<div>
<Select
isDisabled={!isAllowed}
value={value as string}
onValueChange={onChange}
className="w-full border border-mineshaft-500"
dropdownContainerClassName="max-w-none"
isLoading={isGatewaysLoading}
placeholder="Default: Internet Gateway"
position="popper"
>
<SelectItem
value={null as unknown as string}
onClick={() => onChange(undefined)}
<Tooltip
isDisabled={isAllowed}
content="Restricted access. You don't have permission to attach gateways to resources."
>
<div>
<Select
isDisabled={!isAllowed}
value={value as string}
onValueChange={onChange}
className="w-full border border-mineshaft-500"
dropdownContainerClassName="max-w-none"
isLoading={isGatewaysLoading}
placeholder="Default: Internet Gateway"
position="popper"
>
Internet Gateway
</SelectItem>
{gateways?.map((el) => (
<SelectItem value={el.id} key={el.id}>
{el.name}
<SelectItem
value={null as unknown as string}
onClick={() => onChange(undefined)}
>
Internet Gateway
</SelectItem>
))}
</Select>
</div>
</Tooltip>
</FormControl>
)}
/>
)}
</OrgPermissionCan>
{gateways?.map((el) => (
<SelectItem value={el.id} key={el.id}>
{el.name}
</SelectItem>
))}
</Select>
</div>
</Tooltip>
</FormControl>
)}
/>
)}
</OrgPermissionCan>
)}
<Controller
name="method"
control={control}
@@ -6,6 +6,7 @@ import { z } from "zod";
import { OrgPermissionCan } from "@app/components/permissions";
import { Button, FormControl, ModalClose, Select, SelectItem, Tooltip } from "@app/components/v2";
import { useSubscription } from "@app/context";
import {
OrgGatewayPermissionActions,
OrgPermissionSubjects
@@ -75,6 +76,7 @@ export const MySqlConnectionForm = ({ appConnection, onSubmit }: Props) => {
formState: { isSubmitting, isDirty }
} = form;
const { subscription } = useSubscription();
const isPlatformManagedCredentials = appConnection?.isPlatformManagedCredentials ?? false;
const { data: gateways, isPending: isGatewaysLoading } = useQuery(gatewaysQueryKeys.list());
@@ -96,55 +98,57 @@ export const MySqlConnectionForm = ({ appConnection, onSubmit }: Props) => {
}}
>
{!isUpdate && <GenericAppConnectionsFields />}
<OrgPermissionCan
I={OrgGatewayPermissionActions.AttachGateways}
a={OrgPermissionSubjects.Gateway}
>
{(isAllowed) => (
<Controller
control={control}
name="gatewayId"
defaultValue=""
render={({ field: { value, onChange }, fieldState: { error } }) => (
<FormControl
isError={Boolean(error?.message)}
errorText={error?.message}
label="Gateway"
>
<Tooltip
isDisabled={isAllowed}
content="Restricted access. You don't have permission to attach gateways to resources."
{subscription.gateway && (
<OrgPermissionCan
I={OrgGatewayPermissionActions.AttachGateways}
a={OrgPermissionSubjects.Gateway}
>
{(isAllowed) => (
<Controller
control={control}
name="gatewayId"
defaultValue=""
render={({ field: { value, onChange }, fieldState: { error } }) => (
<FormControl
isError={Boolean(error?.message)}
errorText={error?.message}
label="Gateway"
>
<div>
<Select
isDisabled={!isAllowed}
value={value as string}
onValueChange={onChange}
className="w-full border border-mineshaft-500"
dropdownContainerClassName="max-w-none"
isLoading={isGatewaysLoading}
placeholder="Default: Internet Gateway"
position="popper"
>
<SelectItem
value={null as unknown as string}
onClick={() => onChange(undefined)}
<Tooltip
isDisabled={isAllowed}
content="Restricted access. You don't have permission to attach gateways to resources."
>
<div>
<Select
isDisabled={!isAllowed}
value={value as string}
onValueChange={onChange}
className="w-full border border-mineshaft-500"
dropdownContainerClassName="max-w-none"
isLoading={isGatewaysLoading}
placeholder="Default: Internet Gateway"
position="popper"
>
Internet Gateway
</SelectItem>
{gateways?.map((el) => (
<SelectItem value={el.id} key={el.id}>
{el.name}
<SelectItem
value={null as unknown as string}
onClick={() => onChange(undefined)}
>
Internet Gateway
</SelectItem>
))}
</Select>
</div>
</Tooltip>
</FormControl>
)}
/>
)}
</OrgPermissionCan>
{gateways?.map((el) => (
<SelectItem value={el.id} key={el.id}>
{el.name}
</SelectItem>
))}
</Select>
</div>
</Tooltip>
</FormControl>
)}
/>
)}
</OrgPermissionCan>
)}
<Controller
name="method"
control={control}
@@ -6,6 +6,7 @@ import { z } from "zod";
import { OrgPermissionCan } from "@app/components/permissions";
import { Button, FormControl, ModalClose, Select, SelectItem, Tooltip } from "@app/components/v2";
import { useSubscription } from "@app/context";
import {
OrgGatewayPermissionActions,
OrgPermissionSubjects
@@ -75,6 +76,7 @@ export const OracleDBConnectionForm = ({ appConnection, onSubmit }: Props) => {
formState: { isSubmitting, isDirty }
} = form;
const { subscription } = useSubscription();
const isPlatformManagedCredentials = appConnection?.isPlatformManagedCredentials ?? false;
const { data: gateways, isPending: isGatewaysLoading } = useQuery(gatewaysQueryKeys.list());
@@ -96,55 +98,57 @@ export const OracleDBConnectionForm = ({ appConnection, onSubmit }: Props) => {
}}
>
{!isUpdate && <GenericAppConnectionsFields />}
<OrgPermissionCan
I={OrgGatewayPermissionActions.AttachGateways}
a={OrgPermissionSubjects.Gateway}
>
{(isAllowed) => (
<Controller
control={control}
name="gatewayId"
defaultValue=""
render={({ field: { value, onChange }, fieldState: { error } }) => (
<FormControl
isError={Boolean(error?.message)}
errorText={error?.message}
label="Gateway"
>
<Tooltip
isDisabled={isAllowed}
content="Restricted access. You don't have permission to attach gateways to resources."
{subscription.gateway && (
<OrgPermissionCan
I={OrgGatewayPermissionActions.AttachGateways}
a={OrgPermissionSubjects.Gateway}
>
{(isAllowed) => (
<Controller
control={control}
name="gatewayId"
defaultValue=""
render={({ field: { value, onChange }, fieldState: { error } }) => (
<FormControl
isError={Boolean(error?.message)}
errorText={error?.message}
label="Gateway"
>
<div>
<Select
isDisabled={!isAllowed}
value={value as string}
onValueChange={onChange}
className="w-full border border-mineshaft-500"
dropdownContainerClassName="max-w-none"
isLoading={isGatewaysLoading}
placeholder="Default: Internet Gateway"
position="popper"
>
<SelectItem
value={null as unknown as string}
onClick={() => onChange(undefined)}
<Tooltip
isDisabled={isAllowed}
content="Restricted access. You don't have permission to attach gateways to resources."
>
<div>
<Select
isDisabled={!isAllowed}
value={value as string}
onValueChange={onChange}
className="w-full border border-mineshaft-500"
dropdownContainerClassName="max-w-none"
isLoading={isGatewaysLoading}
placeholder="Default: Internet Gateway"
position="popper"
>
Internet Gateway
</SelectItem>
{gateways?.map((el) => (
<SelectItem value={el.id} key={el.id}>
{el.name}
<SelectItem
value={null as unknown as string}
onClick={() => onChange(undefined)}
>
Internet Gateway
</SelectItem>
))}
</Select>
</div>
</Tooltip>
</FormControl>
)}
/>
)}
</OrgPermissionCan>
{gateways?.map((el) => (
<SelectItem value={el.id} key={el.id}>
{el.name}
</SelectItem>
))}
</Select>
</div>
</Tooltip>
</FormControl>
)}
/>
)}
</OrgPermissionCan>
)}
<Controller
name="method"
control={control}
@@ -6,6 +6,7 @@ import { z } from "zod";
import { OrgPermissionCan } from "@app/components/permissions";
import { Button, FormControl, ModalClose, Select, SelectItem, Tooltip } from "@app/components/v2";
import { useSubscription } from "@app/context";
import {
OrgGatewayPermissionActions,
OrgPermissionSubjects
@@ -75,6 +76,7 @@ export const PostgresConnectionForm = ({ appConnection, onSubmit }: Props) => {
formState: { isSubmitting, isDirty }
} = form;
const { subscription } = useSubscription();
const isPlatformManagedCredentials = appConnection?.isPlatformManagedCredentials ?? false;
const { data: gateways, isPending: isGatewaysLoading } = useQuery(gatewaysQueryKeys.list());
@@ -96,55 +98,57 @@ export const PostgresConnectionForm = ({ appConnection, onSubmit }: Props) => {
}}
>
{!isUpdate && <GenericAppConnectionsFields />}
<OrgPermissionCan
I={OrgGatewayPermissionActions.AttachGateways}
a={OrgPermissionSubjects.Gateway}
>
{(isAllowed) => (
<Controller
control={control}
name="gatewayId"
defaultValue=""
render={({ field: { value, onChange }, fieldState: { error } }) => (
<FormControl
isError={Boolean(error?.message)}
errorText={error?.message}
label="Gateway"
>
<Tooltip
isDisabled={isAllowed}
content="Restricted access. You don't have permission to attach gateways to resources."
{subscription.gateway && (
<OrgPermissionCan
I={OrgGatewayPermissionActions.AttachGateways}
a={OrgPermissionSubjects.Gateway}
>
{(isAllowed) => (
<Controller
control={control}
name="gatewayId"
defaultValue=""
render={({ field: { value, onChange }, fieldState: { error } }) => (
<FormControl
isError={Boolean(error?.message)}
errorText={error?.message}
label="Gateway"
>
<div>
<Select
isDisabled={!isAllowed}
value={value as string}
onValueChange={onChange}
className="w-full border border-mineshaft-500"
dropdownContainerClassName="max-w-none"
isLoading={isGatewaysLoading}
placeholder="Default: Internet Gateway"
position="popper"
>
<SelectItem
value={null as unknown as string}
onClick={() => onChange(undefined)}
<Tooltip
isDisabled={isAllowed}
content="Restricted access. You don't have permission to attach gateways to resources."
>
<div>
<Select
isDisabled={!isAllowed}
value={value as string}
onValueChange={onChange}
className="w-full border border-mineshaft-500"
dropdownContainerClassName="max-w-none"
isLoading={isGatewaysLoading}
placeholder="Default: Internet Gateway"
position="popper"
>
Internet Gateway
</SelectItem>
{gateways?.map((el) => (
<SelectItem value={el.id} key={el.id}>
{el.name}
<SelectItem
value={null as unknown as string}
onClick={() => onChange(undefined)}
>
Internet Gateway
</SelectItem>
))}
</Select>
</div>
</Tooltip>
</FormControl>
)}
/>
)}
</OrgPermissionCan>
{gateways?.map((el) => (
<SelectItem value={el.id} key={el.id}>
{el.name}
</SelectItem>
))}
</Select>
</div>
</Tooltip>
</FormControl>
)}
/>
)}
</OrgPermissionCan>
)}
<Controller
name="method"
control={control}
@@ -30,7 +30,8 @@ type BaseFormData = {
isUpdate?: boolean;
};
type GithubFormData = BaseFormData & Pick<TGitHubConnection, "name" | "method" | "description">;
type GithubFormData = BaseFormData &
Pick<TGitHubConnection, "name" | "method" | "description" | "gatewayId" | "credentials">;
type GithubRadarFormData = BaseFormData &
Pick<TGitHubRadarConnection, "name" | "method" | "description">;
@@ -395,7 +396,7 @@ export const OAuthCallbackPage = () => {
clearState(AppConnection.GitHub);
const { connectionId, name, description, returnUrl } = formData;
const { connectionId, name, description, returnUrl, gatewayId, credentials } = formData;
try {
if (connectionId) {
@@ -406,14 +407,18 @@ export const OAuthCallbackPage = () => {
connectionId,
credentials: {
code: code as string,
installationId: installationId as string
}
installationId: installationId as string,
host: credentials.host
},
gatewayId
}
: {
connectionId,
credentials: {
code: code as string
}
code: code as string,
host: credentials.host
},
gatewayId
})
});
} else {
@@ -426,14 +431,18 @@ export const OAuthCallbackPage = () => {
method: GitHubConnectionMethod.App,
credentials: {
code: code as string,
installationId: installationId as string
}
installationId: installationId as string,
host: credentials.host
},
gatewayId
}
: {
method: GitHubConnectionMethod.OAuth,
credentials: {
code: code as string
}
code: code as string,
host: credentials.host
},
gatewayId
})
});
}
@@ -29,6 +29,7 @@ import {
} from "@app/hooks/api/auditLogs/constants";
import { EventType } from "@app/hooks/api/auditLogs/enums";
import { UserAgentType } from "@app/hooks/api/auth/types";
import { Workspace } from "@app/hooks/api/workspace/types";
import { LogFilterItem } from "./LogFilterItem";
import { auditLogFilterFormSchema, Presets, TAuditLogFilterFormData } from "./types";
@@ -43,6 +44,7 @@ type Props = {
presets?: Presets;
setFilter: (data: TAuditLogFilterFormData) => void;
filter: TAuditLogFilterFormData;
project?: Workspace;
};
const getActiveFilterCount = (filter: TAuditLogFilterFormData) => {
@@ -69,7 +71,7 @@ const getActiveFilterCount = (filter: TAuditLogFilterFormData) => {
return filterCount;
};
export const LogsFilter = ({ presets, setFilter, filter }: Props) => {
export const LogsFilter = ({ presets, setFilter, filter, project }: Props) => {
const { data: workspaces = [] } = useGetUserWorkspaces();
const { currentOrg } = useOrganization();
@@ -90,7 +92,7 @@ export const LogsFilter = ({ presets, setFilter, filter }: Props) => {
values: filter
});
const selectedEventTypes = watch("eventType") as EventType[] | undefined;
const selectedProject = watch("project");
const selectedProject = project ?? watch("project");
const showSecretsSection =
selectedEventTypes?.some(
@@ -270,48 +272,50 @@ export const LogsFilter = ({ presets, setFilter, filter }: Props) => {
)}
/>
</LogFilterItem>
<LogFilterItem
label="Project"
onClear={() => {
resetField("project");
resetField("environment");
setValue("secretPath", "");
setValue("secretKey", "");
}}
>
<Controller
control={control}
name="project"
render={({ field: { onChange, value }, fieldState: { error } }) => (
<FormControl
errorText={error?.message}
isError={Boolean(error)}
className="mb-0 w-full"
>
<FilterableSelect
value={value}
isClearable
onChange={(e) => {
if (e === null) {
setValue("secretPath", "");
setValue("secretKey", "");
}
resetField("environment");
onChange(e);
}}
placeholder="All projects"
options={workspacesInOrg.map(({ name, id, type }) => ({
name,
id,
type
}))}
getOptionValue={(option) => option.id}
getOptionLabel={(option) => option.name}
/>
</FormControl>
)}
/>
</LogFilterItem>
{!project && (
<LogFilterItem
label="Project"
onClear={() => {
resetField("project");
resetField("environment");
setValue("secretPath", "");
setValue("secretKey", "");
}}
>
<Controller
control={control}
name="project"
render={({ field: { onChange, value }, fieldState: { error } }) => (
<FormControl
errorText={error?.message}
isError={Boolean(error)}
className="mb-0 w-full"
>
<FilterableSelect
value={value}
isClearable
onChange={(e) => {
if (e === null) {
setValue("secretPath", "");
setValue("secretKey", "");
}
resetField("environment");
onChange(e);
}}
placeholder="All projects"
options={workspacesInOrg.map(({ name, id, type }) => ({
name,
id,
type
}))}
getOptionValue={(option) => option.id}
getOptionLabel={(option) => option.name}
/>
</FormControl>
)}
/>
</LogFilterItem>
)}
<AnimatePresence initial={false}>
{showSecretsSection && (
<motion.div
@@ -7,6 +7,7 @@ import { UpgradePlanModal } from "@app/components/license/UpgradePlanModal";
import { OrgPermissionActions, OrgPermissionSubjects, useSubscription } from "@app/context";
import { Timezone } from "@app/helpers/datetime";
import { withPermission } from "@app/hoc";
import { Workspace } from "@app/hooks/api/workspace/types";
import { usePopUp } from "@app/hooks/usePopUp";
import { LogsDateFilter } from "./LogsDateFilter";
@@ -24,10 +25,11 @@ type Props = {
refetchInterval?: number;
showFilters?: boolean;
pageView?: boolean;
project?: Workspace;
};
export const LogsSection = withPermission(
({ presets, refetchInterval, showFilters = true, pageView = false }: Props) => {
({ presets, refetchInterval, showFilters = true, pageView = false, project }: Props) => {
const { subscription } = useSubscription();
const { popUp, handlePopUpOpen, handlePopUpToggle } = usePopUp(["upgradePlan"] as const);
@@ -83,7 +85,12 @@ export const LogsSection = withPermission(
/>
)}
{showFilters && (
<LogsFilter presets={presets} setFilter={setLogFilter} filter={logFilter} />
<LogsFilter
project={project}
presets={presets}
setFilter={setLogFilter}
filter={logFilter}
/>
)}
</div>
</div>
@@ -94,7 +101,7 @@ export const LogsSection = withPermission(
secretPath: logFilter.secretPath || undefined,
secretKey: logFilter.secretKey || undefined,
eventMetadata: logFilter?.eventMetadata,
projectId: logFilter?.project?.id,
projectId: project?.id || logFilter?.project?.id,
actorType: presets?.actorType,
limit: 15,
eventType: logFilter?.eventType,
@@ -119,7 +126,7 @@ export const LogsSection = withPermission(
return (
<div className="space-y-2">
<div className="flex w-full justify-end">
<div className="flex flex-wrap items-center gap-2 lg:justify-end">
{showFilters && (
<LogsDateFilter
filter={dateFilter}
@@ -52,7 +52,7 @@ export const ProjectsPage = () => {
<title>{t("common.head-title", { title: t("settings.members.title") })}</title>
<link rel="icon" href="/infisical.ico" />
</Helmet>
{!isLoading && !serverDetails?.redisConfigured && (
<div className="mb-4 flex flex-col items-start justify-start text-3xl">
<p className="mb-4 mr-4 font-semibold text-white">Announcements</p>
@@ -9,7 +9,7 @@ import { ProjectMembershipRole } from "@app/hooks/api/roles/types";
import { SelectImportFromPlatformModal } from "./components/SelectImportFromPlatformModal";
export const ImportTab = () => {
export const ExternalMigrationsTab = () => {
const { membership } = useOrgPermission();
const { popUp, handlePopUpOpen, handlePopUpToggle } = usePopUp(["selectImportPlatform"] as const);
@@ -30,7 +30,7 @@ export const ImportTab = () => {
<a
target="_blank"
rel="noopener noreferrer"
href="https://infisical.com/docs/documentation/guides/migrating-from-envkey"
href="https://infisical.com/docs/documentation/platform/external-migrations/overview"
>
<div className="ml-2 inline-block rounded-md bg-yellow/20 px-1.5 pb-[0.03rem] pt-[0.04rem] text-sm text-yellow opacity-80 hover:opacity-100">
<FontAwesomeIcon icon={faBookOpen} className="mr-1.5" />
@@ -45,25 +45,21 @@ export const EnvKeyPlatformModal = ({ onClose }: Props) => {
return;
}
try {
await importEnvKey({
file: data.file,
decryptionKey: data.encryptionKey
});
createNotification({
title: "Import started",
text: "Your data is being imported. You will receive an email when the import is complete or if the import fails. This may take up to 10 minutes.",
type: "info"
});
await importEnvKey({
file: data.file,
decryptionKey: data.encryptionKey
});
createNotification({
title: "Import started",
text: "Your data is being imported. You will receive an email when the import is complete or if the import fails. This may take up to 10 minutes.",
type: "info"
});
onClose();
reset();
onClose();
reset();
if (fileUploadRef.current) {
fileUploadRef.current.value = "";
}
} catch {
reset();
if (fileUploadRef.current) {
fileUploadRef.current.value = "";
}
};
@@ -1,11 +1,12 @@
import { useState } from "react";
import { faKey } from "@fortawesome/free-solid-svg-icons";
import { faKey, faVault } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { AnimatePresence, motion } from "framer-motion";
import { Modal, ModalContent } from "@app/components/v2";
import { EnvKeyPlatformModal } from "./EnvKeyPlatformModal";
import { VaultPlatformModal } from "./VaultPlatformModal";
type Props = {
isOpen?: boolean;
@@ -22,6 +23,11 @@ const PLATFORM_LIST = [
icon: faKey,
platform: "env-key",
title: "Env Key"
},
{
icon: faVault,
platform: "vault",
title: "Vault"
}
] as const;
@@ -82,18 +88,32 @@ export const SelectImportFromPlatformModal = ({ isOpen, onToggle }: Props) => {
</div>
</motion.div>
)}
{wizardStep === WizardSteps.PlatformInputs &&
selectedPlatform?.platform === "env-key" && (
<motion.div
key="env-key-step"
transition={{ duration: 0.1 }}
initial={{ opacity: 0, translateX: 30 }}
animate={{ opacity: 1, translateX: 0 }}
exit={{ opacity: 0, translateX: -30 }}
>
<EnvKeyPlatformModal onClose={() => handleFormReset(false)} />
</motion.div>
)}
{wizardStep === WizardSteps.PlatformInputs && (
<>
{selectedPlatform?.platform === "env-key" && (
<motion.div
key="env-key-step"
transition={{ duration: 0.1 }}
initial={{ opacity: 0, translateX: 30 }}
animate={{ opacity: 1, translateX: 0 }}
exit={{ opacity: 0, translateX: -30 }}
>
<EnvKeyPlatformModal onClose={() => handleFormReset(false)} />
</motion.div>
)}
{selectedPlatform?.platform === "vault" && (
<motion.div
key="vault-step"
transition={{ duration: 0.1 }}
initial={{ opacity: 0, translateX: 30 }}
animate={{ opacity: 1, translateX: 0 }}
exit={{ opacity: 0, translateX: -30 }}
>
<VaultPlatformModal onClose={() => handleFormReset(false)} />
</motion.div>
)}
</>
)}
</AnimatePresence>
</ModalContent>
</Modal>
@@ -0,0 +1,222 @@
import { Controller, useForm } from "react-hook-form";
import { faQuestionCircle } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { zodResolver } from "@hookform/resolvers/zod";
import { twMerge } from "tailwind-merge";
import { z } from "zod";
import { createNotification } from "@app/components/notifications";
import { Button, FormControl, Input, Tooltip } from "@app/components/v2";
import { NoticeBannerV2 } from "@app/components/v2/NoticeBannerV2/NoticeBannerV2";
import { useImportVault } from "@app/hooks/api/migration/mutations";
type Props = {
id?: string;
onClose: () => void;
};
enum VaultMappingType {
KeyVault = "key-vault",
Namespace = "namespace"
}
const MAPPING_TYPE_MENU_ITEMS = [
{
value: VaultMappingType.KeyVault,
label: "Key Vaults",
tooltip: (
<div>
When using key vaults for mapping, each key vault within Vault will be created in Infisical
as a project. Each secret path inside the key vault, will be created as an environment
inside the corresponding project. When using Key Vaults as the project mapping type, a
default environment called &quot;Production&quot; will be created for each project, which
will contain the secrets from the key vault.
<div className="mt-4 flex flex-col gap-1 text-sm">
<div>Key Vault → Project</div>
<div>Default Environment (Production)</div>
<div>Secret Path → Secret Folder</div>
<div>Secret data → Secrets</div>
</div>
</div>
)
},
{
value: VaultMappingType.Namespace,
label: "Namespaces",
tooltip: (
<div>
When using namespaces for mapping, each namespace within Vault will be created in Infisical
as a project. Each key vault (KV) inside the namespace, will be created as an environment
inside the corresponding project.
<div className="mt-4 flex flex-col gap-1 text-sm">
<div>Namespace → Project</div>
<div>Key Vault → Project Environment</div>
<div>Secret Path → Secret Folder</div>
<div>Secret data → Secrets</div>
</div>
</div>
)
}
];
export const VaultPlatformModal = ({ onClose }: Props) => {
const formSchema = z.object({
vaultUrl: z.string().min(1),
vaultNamespace: z.string().trim().optional(),
vaultAccessToken: z.string().min(1),
mappingType: z.nativeEnum(VaultMappingType).default(VaultMappingType.KeyVault)
});
type TFormData = z.infer<typeof formSchema>;
const { mutateAsync: importVault } = useImportVault();
const {
control,
handleSubmit,
reset,
formState: { isLoading, isDirty, isSubmitting, isValid, errors }
} = useForm<TFormData>({
resolver: zodResolver(formSchema)
});
console.log({
isSubmitting,
isLoading,
isValid,
errors
});
const onSubmit = async (data: TFormData) => {
await importVault({
vaultAccessToken: data.vaultAccessToken,
vaultNamespace: data.vaultNamespace,
vaultUrl: data.vaultUrl,
mappingType: data.mappingType
});
createNotification({
title: "Import started",
text: "Your data is being imported. You will receive an email when the import is complete or if the import fails. This may take up to 10 minutes.",
type: "info"
});
onClose();
reset();
};
return (
<div>
<NoticeBannerV2 title="Vault KV Secret Engine Import" className="mb-4">
<p className="text-sm">
The Vault migration currently supports importing static secrets from Vault
Dedicated/Self-Hosted.
<div className="mt-2 text-xs opacity-80">
Currently only KV Secret Engine V2 is supported for Vault migrations.
</div>
</p>
</NoticeBannerV2>
<form onSubmit={handleSubmit(onSubmit)} autoComplete="off">
<Controller
control={control}
name="vaultUrl"
render={({ field, fieldState: { error } }) => (
<FormControl
label="Vault URL"
isRequired
errorText={error?.message}
isError={Boolean(error)}
>
<Input placeholder="" {...field} />
</FormControl>
)}
/>
<Controller
control={control}
name="vaultNamespace"
render={({ field, fieldState: { error } }) => (
<FormControl
label="Vault Namespace"
errorText={error?.message}
isError={Boolean(error)}
>
<Input type="text" placeholder="" {...field} />
</FormControl>
)}
/>
<Controller
control={control}
name="vaultAccessToken"
render={({ field, fieldState: { error } }) => (
<FormControl
label="Vault Access Token"
isRequired
errorText={error?.message}
isError={Boolean(error)}
>
<Input type="password" placeholder="" {...field} />
</FormControl>
)}
/>
<Controller
control={control}
name="mappingType"
defaultValue={VaultMappingType.KeyVault}
render={({ field, fieldState: { error } }) => (
<FormControl
label="Project Mapping"
isError={Boolean(error)}
isRequired
errorText={error?.message}
className="flex-1"
>
<div className="mt-2 grid h-full w-full grid-cols-2 gap-4">
{MAPPING_TYPE_MENU_ITEMS.map((el) => (
<div
key={el.value}
className={twMerge(
"flex w-full cursor-pointer flex-col items-center gap-2 rounded border border-mineshaft-600 p-4 opacity-75 transition-all",
field.value === el.value
? "border-primary-700 border-opacity-70 bg-mineshaft-600 opacity-100"
: "hover:border-primary-700 hover:bg-mineshaft-600"
)}
onClick={() => field.onChange(el.value)}
role="button"
tabIndex={0}
onKeyDown={(e) => {
if (e.key === "Enter") {
field.onChange(el.value);
}
}}
>
<div className="flex items-center gap-1">
<div className="text-center text-sm">{el.label}</div>
{el.tooltip && (
<div className="text-center text-sm">
<Tooltip content={el.tooltip} className="max-w-96">
<FontAwesomeIcon className="opacity-60" icon={faQuestionCircle} />
</Tooltip>
</div>
)}
</div>
</div>
))}
</div>
</FormControl>
)}
/>
<div className="mt-6 flex items-center space-x-4">
<Button
type="submit"
isLoading={isLoading}
isDisabled={!isDirty || isSubmitting || isLoading || !isValid}
>
Import data
</Button>
<Button variant="outline_bg" onClick={onClose}>
Cancel
</Button>
</div>
</form>
</div>
);
};
@@ -0,0 +1 @@
export { ExternalMigrationsTab } from "./ExternalMigrationsTab";
@@ -1 +0,0 @@
export { ImportTab } from "./ImportTab";
@@ -5,7 +5,7 @@ import { Tab, TabList, TabPanel, Tabs } from "@app/components/v2";
import { ROUTE_PATHS } from "@app/const/routes";
import { AuditLogStreamsTab } from "../AuditLogStreamTab";
import { ImportTab } from "../ImportTab";
import { ExternalMigrationsTab } from "../ExternalMigrationsTab";
import { KmipTab } from "../KmipTab/OrgKmipTab";
import { OrgEncryptionTab } from "../OrgEncryptionTab";
import { OrgGeneralTab } from "../OrgGeneralTab";
@@ -39,7 +39,11 @@ export const OrgTabGroup = () => {
component: OrgWorkflowIntegrationTab
},
{ name: "Audit Log Streams", key: "tag-audit-log-streams", component: AuditLogStreamsTab },
{ name: "Import", key: "tab-import", component: ImportTab },
{
name: "External Migrations",
key: "tab-external-migrations",
component: ExternalMigrationsTab
},
{
name: "Project Templates",
key: "project-templates",
@@ -155,8 +155,8 @@ export const SpecificPrivilegeSecretForm = ({
const selectablePaths = useMemo(() => {
if (!policies) return [];
const environmentPolicies = policies.filter(
(policy) => policy.environment.slug === selectedEnvironment
const environmentPolicies = policies.filter((policy) =>
policy.environments.find((env) => env.slug === selectedEnvironment)
);
privilegeForm.setValue("secretPath", "", {
@@ -0,0 +1,27 @@
import { Helmet } from "react-helmet";
import { PageHeader } from "@app/components/v2";
import { useWorkspace } from "@app/context";
import { LogsSection } from "@app/pages/organization/AuditLogsPage/components";
export const AuditLogsPage = () => {
const { currentWorkspace } = useWorkspace();
return (
<div className="container mx-auto flex flex-col justify-between bg-bunker-800 text-white">
<Helmet>
<title>Project Audit Logs</title>
<link rel="icon" href="/infisical.ico" />
</Helmet>
<div className="flex h-full w-full justify-center bg-bunker-800 text-white">
<div className="w-full max-w-7xl">
<PageHeader
title="Audit logs"
description="Audit logs for security and compliance teams to monitor information access."
/>
<LogsSection pageView project={currentWorkspace} />
</div>
</div>
</div>
);
};
@@ -0,0 +1,19 @@
import { createFileRoute } from "@tanstack/react-router";
import { AuditLogsPage } from "./AuditLogsPage";
export const Route = createFileRoute(
"/_authenticate/_inject-org-details/_org-layout/projects/cert-management/$projectId/_cert-manager-layout/audit-logs"
)({
component: AuditLogsPage,
beforeLoad: ({ context }) => {
return {
breadcrumbs: [
...context.breadcrumbs,
{
label: "Audit Logs"
}
]
};
}
});
@@ -0,0 +1,19 @@
import { createFileRoute } from "@tanstack/react-router";
import { AuditLogsPage } from "./AuditLogsPage";
export const Route = createFileRoute(
"/_authenticate/_inject-org-details/_org-layout/projects/kms/$projectId/_kms-layout/audit-logs"
)({
component: AuditLogsPage,
beforeLoad: ({ context }) => {
return {
breadcrumbs: [
...context.breadcrumbs,
{
label: "Audit Logs"
}
]
};
}
});
@@ -0,0 +1,19 @@
import { createFileRoute } from "@tanstack/react-router";
import { AuditLogsPage } from "./AuditLogsPage";
export const Route = createFileRoute(
"/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/audit-logs"
)({
component: AuditLogsPage,
beforeLoad: ({ context }) => {
return {
breadcrumbs: [
...context.breadcrumbs,
{
label: "Audit Logs"
}
]
};
}
});
@@ -0,0 +1,19 @@
import { createFileRoute } from "@tanstack/react-router";
import { AuditLogsPage } from "./AuditLogsPage";
export const Route = createFileRoute(
"/_authenticate/_inject-org-details/_org-layout/projects/secret-scanning/$projectId/_secret-scanning-layout/audit-logs"
)({
component: AuditLogsPage,
beforeLoad: ({ context }) => {
return {
breadcrumbs: [
...context.breadcrumbs,
{
label: "Audit Logs"
}
]
};
}
});
@@ -0,0 +1,19 @@
import { createFileRoute } from "@tanstack/react-router";
import { AuditLogsPage } from "./AuditLogsPage";
export const Route = createFileRoute(
"/_authenticate/_inject-org-details/_org-layout/projects/ssh/$projectId/_ssh-layout/audit-logs"
)({
component: AuditLogsPage,
beforeLoad: ({ context }) => {
return {
breadcrumbs: [
...context.breadcrumbs,
{
label: "Audit Logs"
}
]
};
}
});
@@ -1,5 +1,5 @@
import { useRenderConnectionListServices } from "@app/hooks/api/appConnections/render";
import { TRenderSync } from "@app/hooks/api/secretSyncs/render-sync";
import { TRenderSync } from "@app/hooks/api/secretSyncs/types/render-sync";
import { getSecretSyncDestinationColValues } from "../helpers";
import { SecretSyncTableCell } from "../SecretSyncTableCell";
@@ -10,13 +10,12 @@ import {
faArrowRight,
faArrowRightToBracket,
faArrowUp,
faFileImport,
faFilter,
faFingerprint,
faFolder,
faFolderBlank,
faFolderPlus,
faKey,
faList,
faPlus,
faRotate
} from "@fortawesome/free-solid-svg-icons";
@@ -96,7 +95,12 @@ 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 { TSecretRotationV2 } from "@app/hooks/api/secretRotationsV2";
import { SecretType, SecretV3RawSanitized, TSecretFolder } from "@app/hooks/api/types";
import {
SecretType,
SecretV3RawSanitized,
TSecretFolder,
WorkspaceEnv
} from "@app/hooks/api/types";
import { ProjectVersion } from "@app/hooks/api/workspace/types";
import {
useDynamicSecretOverview,
@@ -135,7 +139,6 @@ enum RowType {
Folder = "folder",
DynamicSecret = "dynamic",
Secret = "secret",
Import = "import",
SecretRotation = "rotation"
}
@@ -144,11 +147,10 @@ type Filter = {
};
const DEFAULT_FILTER_STATE = {
[RowType.Folder]: true,
[RowType.DynamicSecret]: true,
[RowType.Secret]: true,
[RowType.Import]: true,
[RowType.SecretRotation]: true
[RowType.Folder]: false,
[RowType.DynamicSecret]: false,
[RowType.Secret]: false,
[RowType.SecretRotation]: false
};
const DEFAULT_COLLAPSED_HEADER_HEIGHT = 120;
@@ -265,11 +267,8 @@ export const OverviewPage = () => {
)
);
const [visibleEnvs, setVisibleEnvs] = useState(userAvailableEnvs);
useEffect(() => {
setVisibleEnvs(userAvailableEnvs);
}, [userAvailableEnvs]);
const [filteredEnvs, setFilteredEnvs] = useState<WorkspaceEnv[]>([]);
const visibleEnvs = filteredEnvs.length ? filteredEnvs : userAvailableEnvs;
const {
secretImports,
@@ -282,6 +281,7 @@ export const OverviewPage = () => {
environments: (userAvailableEnvs || []).map(({ slug }) => slug)
});
const isFilteredByResources = Object.values(filter).some(Boolean);
const { isPending: isOverviewLoading, data: overview } = useGetProjectSecretsOverview(
{
projectId: workspaceId,
@@ -289,11 +289,11 @@ export const OverviewPage = () => {
secretPath,
orderDirection,
orderBy,
includeFolders: filter.folder,
includeDynamicSecrets: filter.dynamic,
includeSecrets: filter.secret,
includeImports: filter.import,
includeSecretRotations: filter.rotation,
includeFolders: isFilteredByResources ? filter.folder : true,
includeDynamicSecrets: isFilteredByResources ? filter.dynamic : true,
includeSecrets: isFilteredByResources ? filter.secret : true,
includeImports: true,
includeSecretRotations: isFilteredByResources ? filter.rotation : true,
search: debouncedSearchFilter,
limit,
offset
@@ -529,10 +529,10 @@ export const OverviewPage = () => {
};
const handleEnvSelect = (envId: string) => {
if (visibleEnvs.map((env) => env.id).includes(envId)) {
setVisibleEnvs(visibleEnvs.filter((env) => env.id !== envId));
if (filteredEnvs.map((env) => env.id).includes(envId)) {
setFilteredEnvs(filteredEnvs.filter((env) => env.id !== envId));
} else {
setVisibleEnvs(visibleEnvs.concat(userAvailableEnvs.filter((env) => env.id === envId)));
setFilteredEnvs(filteredEnvs.concat(userAvailableEnvs.filter((env) => env.id === envId)));
}
};
@@ -792,11 +792,11 @@ export const OverviewPage = () => {
envNames: string[],
envs: { environment: string; importedBy: ProjectSecretsImportedBy[] }[]
): ProjectSecretsImportedBy[] => {
const filteredEnvs = envs.filter((env) => envNames.includes(env.environment));
const environments = envs.filter((env) => envNames.includes(env.environment));
if (filteredEnvs.length === 0) return [];
if (environments.length === 0) return [];
const allImportedBy = filteredEnvs.flatMap((env) => env.importedBy);
const allImportedBy = environments.flatMap((env) => env.importedBy);
const groupedBySlug: Record<string, ProjectSecretsImportedBy[]> = {};
allImportedBy.forEach((item) => {
@@ -902,9 +902,7 @@ export const OverviewPage = () => {
const isTableEmpty = totalCount === 0;
const isTableFiltered =
Boolean(Object.values(filter).filter((enabled) => !enabled).length) ||
userAvailableEnvs.length !== visibleEnvs.length;
const isTableFiltered = isFilteredByResources || filteredEnvs.length > 0;
if (!isProjectV3)
return (
@@ -969,26 +967,42 @@ export const OverviewPage = () => {
<div className="mt-4 flex items-center justify-between">
<FolderBreadCrumbs secretPath={secretPath} onResetSearch={handleResetSearch} />
<div className="flex flex-row items-center justify-center space-x-2">
{isTableFiltered && (
<Button
variant="plain"
colorSchema="secondary"
onClick={() => {
setFilteredEnvs([]);
setFilter(DEFAULT_FILTER_STATE);
}}
>
Clear Filters
</Button>
)}
{userAvailableEnvs.length > 0 && (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<IconButton
ariaLabel="Environments"
variant="plain"
<Button
size="sm"
variant="outline_bg"
className={twMerge(
"flex h-10 w-11 items-center justify-center overflow-hidden border border-mineshaft-600 bg-mineshaft-800 p-0 transition-all hover:border-primary/60 hover:bg-primary/10",
isTableFiltered && "border-primary/50 text-primary"
"flex h-[2.5rem]",
isTableFiltered && "border-primary/40 bg-primary/10"
)}
leftIcon={
<FontAwesomeIcon
icon={faFilter}
className={isTableFiltered ? "text-primary/80" : undefined}
/>
}
>
<Tooltip content="Choose visible environments" className="mb-2">
<FontAwesomeIcon icon={faList} />
</Tooltip>
</IconButton>
Filters
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent
className="thin-scrollbar max-h-[70vh] overflow-y-auto"
align="end"
sideOffset={2}
>
{/* <DropdownMenuItem className="px-1.5" asChild>
<Button
@@ -1002,20 +1016,8 @@ export const OverviewPage = () => {
Create an environment
</Button>
</DropdownMenuItem> */}
<DropdownMenuLabel>Filter project resources</DropdownMenuLabel>
<DropdownMenuItem
onClick={(e) => {
e.preventDefault();
handleToggleRowType(RowType.Import);
}}
icon={filter[RowType.Import] && <FontAwesomeIcon icon={faCheckCircle} />}
iconPos="right"
>
<div className="flex items-center gap-2">
<FontAwesomeIcon icon={faFileImport} className="text-green-700" />
<span>Imports</span>
</div>
</DropdownMenuItem>
<DropdownMenuLabel>Filter by Resource</DropdownMenuLabel>
<DropdownMenuItem
onClick={(e) => {
e.preventDefault();
@@ -1070,11 +1072,11 @@ export const OverviewPage = () => {
<span>Secrets</span>
</div>
</DropdownMenuItem>
<DropdownMenuLabel>Choose visible environments</DropdownMenuLabel>
<DropdownMenuLabel>Filter by Environment</DropdownMenuLabel>
{userAvailableEnvs.map((availableEnv) => {
const { id: envId, name } = availableEnv;
const isEnvSelected = visibleEnvs.map((env) => env.id).includes(envId);
const isEnvSelected = filteredEnvs.map((env) => env.id).includes(envId);
return (
<DropdownMenuItem
onClick={(e) => {
@@ -1082,7 +1084,6 @@ export const OverviewPage = () => {
handleEnvSelect(envId);
}}
key={envId}
disabled={visibleEnvs?.length === 1}
icon={isEnvSelected && <FontAwesomeIcon icon={faCheckCircle} />}
iconPos="right"
>
@@ -1405,13 +1406,6 @@ export const OverviewPage = () => {
className="bg-mineshaft-700"
/>
)}
{userAvailableEnvs.length > 0 && visibleEnvs.length === 0 && (
<Tr>
<Td colSpan={visibleEnvs.length + 1}>
<EmptyState title="You have no visible environments" iconSize="3x" />
</Td>
</Tr>
)}
{userAvailableEnvs.length === 0 && (
<Tr>
<Td colSpan={visibleEnvs.length + 1}>
@@ -1444,8 +1438,8 @@ export const OverviewPage = () => {
<Td colSpan={visibleEnvs.length + 1}>
<EmptyState
title={
debouncedSearchFilter
? "No secret found for your search, add one now"
isTableFiltered || debouncedSearchFilter
? "No secrets found for your search, add one now"
: "Let's add some secrets"
}
icon={faFolderBlank}
@@ -166,17 +166,20 @@ export const ApprovalPolicyList = ({ workspaceId }: IProps) => {
const filteredPolicies = useMemo(
() =>
policies
.filter(({ policyType, environment, name, secretPath }) => {
.filter(({ policyType, environments, name, secretPath }) => {
if (filters.type && policyType !== filters.type) return false;
if (filters.environmentIds.length && !filters.environmentIds.includes(environment.id))
if (
filters.environmentIds.length &&
!environments.some((env) => filters.environmentIds.includes(env.id))
)
return false;
const searchValue = search.trim().toLowerCase();
return (
name.toLowerCase().includes(searchValue) ||
environment.name.toLowerCase().includes(searchValue) ||
environments.some((env) => env.name.toLowerCase().includes(searchValue)) ||
(secretPath ?? "*").toLowerCase().includes(searchValue)
);
})
@@ -189,9 +192,18 @@ export const ApprovalPolicyList = ({ workspaceId }: IProps) => {
.toLowerCase()
.localeCompare(policyTwo.policyType.toLowerCase());
case PolicyOrderBy.Environment:
return policyOne.environment.name
.toLowerCase()
.localeCompare(policyTwo.environment.name.toLowerCase());
// eslint-disable-next-line no-case-declarations
const getFirstEnvName = (policy: { environments: { name: string }[] }) => {
if (!policy.environments?.length) return "";
return (
policy.environments
.map((env) => env.name?.toLowerCase() || "")
.filter((name) => name)
.sort()[0] || ""
);
};
return getFirstEnvName(policyOne).localeCompare(getFirstEnvName(policyTwo));
case PolicyOrderBy.SecretPath:
return (policyOne.secretPath ?? "*")
.toLowerCase()
@@ -54,7 +54,7 @@ type Props = {
const formSchema = z
.object({
environment: z.object({ slug: z.string(), name: z.string() }),
environments: z.array(z.object({ slug: z.string(), name: z.string() })).min(1),
name: z.string().optional(),
secretPath: z.string().trim().min(1),
approvals: z.number().min(1).default(1),
@@ -134,7 +134,7 @@ const Form = ({
values: editValues
? ({
...editValues,
environment: editValues.environment,
environments: editValues.environments,
userApprovers:
editValues?.approvers
?.filter((approver) => approver.type === ApproverType.User)
@@ -191,7 +191,7 @@ const Form = ({
const { currentWorkspace } = useWorkspace();
const { data: groups } = useListWorkspaceGroups(projectId);
const environments = currentWorkspace?.environments || [];
const availableEnvironments = currentWorkspace?.environments || [];
const isAccessPolicyType = watch("policyType") === PolicyType.AccessPolicy;
const { mutateAsync: createAccessApprovalPolicy } = useCreateAccessApprovalPolicy();
@@ -204,11 +204,11 @@ const Form = ({
const formUserBypassers = watch("userBypassers");
const formGroupBypassers = watch("groupBypassers");
const formEnvironment = watch("environment")?.slug;
const formEnvironments = watch("environments");
const bypasserCount = (formUserBypassers || []).length + (formGroupBypassers || []).length;
const handleCreatePolicy = async ({
environment,
environments,
groupApprovers,
userApprovers,
groupBypassers,
@@ -226,7 +226,7 @@ const Form = ({
...data,
approvers: [...userApprovers, ...groupApprovers],
bypassers: bypassers.length > 0 ? bypassers : undefined,
environment: environment.slug,
environments: environments.map((env) => env.slug),
workspaceId: currentWorkspace?.id || ""
});
} else {
@@ -242,7 +242,7 @@ const Form = ({
numberOfApprovals: el.approvals
})),
bypassers: bypassers.length > 0 ? bypassers : undefined,
environment: environment.slug,
environments: environments.map((env) => env.slug),
projectSlug
});
}
@@ -261,7 +261,7 @@ const Form = ({
};
const handleUpdatePolicy = async ({
environment,
environments,
userApprovers,
groupApprovers,
userBypassers,
@@ -281,7 +281,8 @@ const Form = ({
...data,
approvers: [...userApprovers, ...groupApprovers],
bypassers: bypassers.length > 0 ? bypassers : undefined,
workspaceId: currentWorkspace?.id || ""
workspaceId: currentWorkspace?.id || "",
environments: environments.map((env) => env.slug)
});
} else {
await updateAccessApprovalPolicy({
@@ -297,7 +298,7 @@ const Form = ({
numberOfApprovals: el.approvals
})),
bypassers: bypassers.length > 0 ? bypassers : undefined,
environment: environment.slug,
environments: environments.map((env) => env.slug),
projectSlug
});
}
@@ -479,28 +480,28 @@ const Form = ({
<SecretPathInput
{...field}
value={field.value || ""}
environment={formEnvironment}
environment={formEnvironments?.[0]?.slug || ""}
/>
</FormControl>
)}
/>
<Controller
control={control}
name="environment"
name="environments"
render={({ field: { value, onChange }, fieldState: { error } }) => (
<FormControl
label="Environment"
label="Environments"
isRequired
isError={Boolean(error)}
errorText={error?.message}
className="flex-1"
>
<FilterableSelect
isDisabled={isEditMode}
value={value}
isMulti
onChange={onChange}
placeholder="Select environment..."
options={environments}
placeholder="Select environments..."
options={availableEnvironments}
getOptionValue={(option) => option.slug}
getOptionLabel={(option) => option.name}
/>
@@ -37,7 +37,7 @@ import { TWorkspaceUser } from "@app/hooks/api/users/types";
interface IPolicy {
id: string;
name: string;
environment: WorkspaceEnv;
environments: WorkspaceEnv[];
projectId?: string;
secretPath?: string;
approvals: number;
@@ -112,7 +112,7 @@ export const ApprovalPolicyRow = ({
onClick={() => setIsExpanded.toggle()}
>
<Td>{policy.name || <span className="text-mineshaft-400">Unnamed Policy</span>}</Td>
<Td>{policy.environment.name}</Td>
<Td>{policy.environments.map((env) => env.name).join(", ")}</Td>
<Td>{policy.secretPath || "*"}</Td>
<Td>
<Badge
@@ -1,5 +1,5 @@
/* eslint-disable no-case-declarations */
import { useCallback, useEffect, useMemo, useState } from "react";
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { Helmet } from "react-helmet";
import { useTranslation } from "react-i18next";
import { subject } from "@casl/ability";
@@ -53,6 +53,7 @@ import { PendingAction } from "@app/hooks/api/secretFolders/types";
import { useCreateCommit } from "@app/hooks/api/secrets/mutations";
import { SecretV3RawSanitized } from "@app/hooks/api/types";
import { usePathAccessPolicies } from "@app/hooks/usePathAccessPolicies";
import { useResizableColWidth } from "@app/hooks/useResizableColWidth";
import { hasSecretReadValueOrDescribePermission } from "@app/lib/fn/permission";
import { RequestAccessModal } from "@app/pages/secret-manager/SecretApprovalsPage/components/AccessApprovalRequest/components/RequestAccessModal";
import { SecretRotationListView } from "@app/pages/secret-manager/SecretDashboardPage/components/SecretRotationListView";
@@ -104,6 +105,8 @@ const Page = () => {
const { permission } = useProjectPermission();
const { mutateAsync: createCommit } = useCreateCommit();
const tableRef = useRef<HTMLDivElement>(null);
const [isVisible, setIsVisible] = useState(false);
const { isBatchMode, pendingChanges } = useBatchMode();
const { loadPendingChanges, setExistingKeys } = useBatchModeActions();
@@ -212,11 +215,11 @@ const Page = () => {
searchFilter: (routerQueryParams.search as string) || "",
// these should always be on by default for the UI, they will be disabled for the query below based off permissions
include: {
[RowType.Folder]: true,
[RowType.Import]: true,
[RowType.DynamicSecret]: true,
[RowType.Secret]: true,
[RowType.SecretRotation]: true
[RowType.Folder]: false,
[RowType.Import]: false,
[RowType.DynamicSecret]: false,
[RowType.Secret]: false,
[RowType.SecretRotation]: false
}
};
@@ -242,6 +245,7 @@ const Page = () => {
}
}, [currentWorkspace, environment]);
const isResourceTypeFiltered = Object.values(filter.include).some(Boolean);
const {
data,
isPending: isDetailsLoading,
@@ -255,12 +259,14 @@ const Page = () => {
orderBy,
search: debouncedSearchFilter,
orderDirection,
includeImports: canReadSecretImports && filter.include.import,
includeFolders: filter.include.folder,
includeImports: canReadSecretImports && (isResourceTypeFiltered ? filter.include.import : true),
includeFolders: isResourceTypeFiltered ? filter.include.folder : true,
viewSecretValue: canReadSecretValue,
includeDynamicSecrets: canReadDynamicSecret && filter.include.dynamic,
includeSecrets: canReadSecret && filter.include.secret,
includeSecretRotations: canReadSecretRotations && filter.include.rotation,
includeDynamicSecrets:
canReadDynamicSecret && (isResourceTypeFiltered ? filter.include.dynamic : true),
includeSecrets: canReadSecret && (isResourceTypeFiltered ? filter.include.secret : true),
includeSecretRotations:
canReadSecretRotations && (isResourceTypeFiltered ? filter.include.rotation : true),
tags: filter.tags
});
@@ -480,6 +486,14 @@ const Page = () => {
handlePopUpClose("snapshots");
}, []);
const { handleMouseDown, isResizing, colWidth } = useResizableColWidth({
initialWidth: 320,
minWidth: 100,
maxWidth: tableRef.current
? tableRef.current.clientWidth - 148 // ensure value column can't collapse completely
: 800
});
useEffect(() => {
// restore filters for path if set
const restore = filterHistory.get(secretPath);
@@ -769,8 +783,22 @@ const Page = () => {
isPITEnabled={isPITEnabled}
hasPathPolicies={hasPathPolicies}
onRequestAccess={(params) => handlePopUpOpen("requestAccess", params)}
onClearFilters={() =>
setFilter((prev) => ({
...prev,
tags: {},
include: {
secret: false,
import: false,
dynamic: false,
rotation: false,
folder: false
}
}))
}
/>
<div
ref={tableRef}
className={twMerge(
"thin-scrollbar mt-3 overflow-y-auto overflow-x-hidden rounded-md bg-mineshaft-800 text-left text-sm text-bunker-300",
isNotEmpty && "rounded-b-none"
@@ -804,20 +832,34 @@ const Page = () => {
/>
</div>
</Tooltip>
<div
className="flex w-80 flex-shrink-0 items-center border-r border-mineshaft-600 py-2 pl-4"
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 className="relative">
<div
tabIndex={-1}
role="button"
className={`absolute -right-[0.05rem] z-40 h-full w-0.5 cursor-ew-resize hover:bg-blue-400/20 ${
isResizing ? "bg-blue-400/75" : "bg-transparent"
}`}
onMouseDown={handleMouseDown}
/>
<div className="pointer-events-none absolute -right-[0.04rem] top-2 z-30">
<div className="h-5 w-0.5 rounded-[1.5px] bg-gray-400 opacity-50" />
</div>
<div
className="flex flex-shrink-0 items-center border-r border-mineshaft-600 py-2 pl-4"
style={{ width: colWidth }}
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>
<div className="flex-grow px-4 py-2">Value</div>
</div>
@@ -911,6 +953,7 @@ const Page = () => {
)}
{canReadSecret && Boolean(mergedSecrets?.length) && (
<SecretListView
colWidth={colWidth}
secrets={mergedSecrets}
tags={tags}
isVisible={isVisible}
@@ -932,12 +975,12 @@ const Page = () => {
/>
)}
{noAccessSecretCount > 0 && <SecretNoAccessListView count={noAccessSecretCount} />}
{!canReadSecret &&
!canReadDynamicSecret &&
!canReadSecretImports &&
folders?.length === 0 && <PermissionDeniedBanner />}
</div>
</div>
{!canReadSecret &&
!canReadDynamicSecret &&
!canReadSecretImports &&
folders?.length === 0 && <PermissionDeniedBanner />}
{!isDetailsLoading &&
(totalCount > 0 ||
pendingChanges.secrets.length > 0 ||
@@ -136,6 +136,7 @@ type Props = {
isPITEnabled: boolean;
onRequestAccess: (actions: ProjectPermissionActions[]) => void;
hasPathPolicies: boolean;
onClearFilters: () => void;
};
export const ActionBar = ({
@@ -159,7 +160,8 @@ export const ActionBar = ({
isPITEnabled = false,
usedBySecretSyncs,
onRequestAccess,
hasPathPolicies
hasPathPolicies,
onClearFilters
}: Props) => {
const { handlePopUpOpen, handlePopUpToggle, handlePopUpClose, popUp } = usePopUp([
"addFolder",
@@ -661,6 +663,9 @@ export const ActionBar = ({
}
};
const isTableFiltered =
Object.values(filter.tags).some(Boolean) || Object.values(filter.include).some(Boolean);
return (
<>
<div className="mt-4 flex items-center space-x-2">
@@ -676,18 +681,22 @@ export const ActionBar = ({
<div>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<IconButton
<Button
size="sm"
variant="outline_bg"
ariaLabel="Download"
className={twMerge(
"transition-all",
(Object.keys(filter.tags).length ||
Object.values(filter.include).filter((include) => !include).length) &&
"border-primary/50 text-primary"
"flex h-[2.5rem]",
isTableFiltered && "border-primary/40 bg-primary/10"
)}
leftIcon={
<FontAwesomeIcon
icon={faFilter}
className={isTableFiltered ? "text-primary/80" : undefined}
/>
}
>
<FontAwesomeIcon icon={faFilter} />
</IconButton>
Filters
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end" className="p-0">
<DropdownMenuGroup>Filter By</DropdownMenuGroup>
@@ -762,51 +771,62 @@ export const ActionBar = ({
<span>Secrets</span>
</div>
</DropdownMenuItem>
<DropdownSubMenu>
<DropdownSubMenuTrigger
iconPos="right"
icon={<FontAwesomeIcon icon={faChevronRight} size="sm" />}
>
Tags
</DropdownSubMenuTrigger>
<DropdownSubMenuContent className="thin-scrollbar max-h-[20rem] overflow-y-auto rounded-l-none">
<DropdownMenuLabel className="sticky top-0 bg-mineshaft-900">
Apply Tags to Filter Secrets
</DropdownMenuLabel>
{tags.map(({ id, slug, color }) => (
<DropdownMenuItem
onClick={(evt) => {
evt.preventDefault();
onToggleTagFilter(slug);
}}
key={id}
icon={filter?.tags[slug] && <FontAwesomeIcon icon={faCheckCircle} />}
iconPos="right"
>
<div className="flex items-center">
<div
className="mr-2 h-2 w-2 rounded-full"
style={{ background: color || "#bec2c8" }}
/>
{slug}
</div>
</DropdownMenuItem>
))}
</DropdownSubMenuContent>
</DropdownSubMenu>
{Boolean(tags.length) && (
<DropdownSubMenu>
<DropdownSubMenuTrigger
iconPos="right"
icon={<FontAwesomeIcon icon={faChevronRight} size="sm" />}
>
Tags
</DropdownSubMenuTrigger>
<DropdownSubMenuContent className="thin-scrollbar max-h-[20rem] overflow-y-auto rounded-l-none">
<DropdownMenuLabel className="sticky top-0 bg-mineshaft-900">
Apply Tags to Filter Secrets
</DropdownMenuLabel>
{tags.map(({ id, slug, color }) => (
<DropdownMenuItem
onClick={(evt) => {
evt.preventDefault();
onToggleTagFilter(slug);
}}
key={id}
icon={filter?.tags[slug] && <FontAwesomeIcon icon={faCheckCircle} />}
iconPos="right"
>
<div className="flex items-center">
<div
className="mr-2 h-2 w-2 rounded-full"
style={{ background: color || "#bec2c8" }}
/>
{slug}
</div>
</DropdownMenuItem>
))}
</DropdownSubMenuContent>
</DropdownSubMenu>
)}
</DropdownMenuContent>
</DropdownMenu>
</div>
{isTableFiltered && (
<Button variant="plain" colorSchema="secondary" onClick={onClearFilters}>
Clear Filters
</Button>
)}
<div className="flex-grow" />
<div>
{isProtectedBranch && (
<Tooltip content={`Protected by policy ${protectedBranchPolicyName}`}>
<IconButton variant="outline_bg" ariaLabel="protected">
<IconButton
variant="outline_bg"
className="border-primary/40 bg-primary/10"
ariaLabel="protected"
>
<FontAwesomeIcon icon={faLock} className="text-primary" />
</IconButton>
</Tooltip>
)}
</div>
<div className="flex-grow" />
<div>
<IconButton variant="outline_bg" ariaLabel="Download" onClick={handleSecretDownload}>
<FontAwesomeIcon icon={faDownload} />
@@ -235,7 +235,7 @@ export const FolderListView = ({
)}
</div>
{isPending ? (
<div className="flex items-center space-x-4 border-l border-mineshaft-600 px-3 py-3">
<div className="flex w-16 items-center justify-between border-l border-mineshaft-600 px-3 py-3">
<IconButton
ariaLabel="edit-folder"
variant="plain"
@@ -59,7 +59,6 @@ import { ActorType } from "@app/hooks/api/auditLogs/enums";
import { useGetReminder } from "@app/hooks/api/reminders";
import { useGetSecretAccessList } from "@app/hooks/api/secrets/queries";
import { SecretV3RawSanitized, WsTag } from "@app/hooks/api/types";
import { ProjectType } from "@app/hooks/api/workspace/types";
import { hasSecretReadValueOrDescribePermission } from "@app/lib/fn/permission";
import { camelCaseToSpaces } from "@app/lib/fn/string";
@@ -269,9 +268,9 @@ export const SecretDetailSidebar = ({
) => {
switch (actorType) {
case ActorType.USER:
return `/${ProjectType.SecretManager}/${currentWorkspace.id}/members/${membershipId}`;
return `/projects/secret-management/${currentWorkspace.id}/members/${membershipId}`;
case ActorType.IDENTITY:
return `/${ProjectType.SecretManager}/${currentWorkspace.id}/identities/${actorId}`;
return `/projects/secret-management/${currentWorkspace.id}/identities/${actorId}`;
default:
return null;
}
@@ -90,6 +90,7 @@ type Props = {
}[];
isPending?: boolean;
pendingAction?: PendingAction;
colWidth: number;
};
export const SecretItem = memo(
@@ -108,7 +109,8 @@ export const SecretItem = memo(
handleSecretShare,
importedBy,
isPending,
pendingAction
pendingAction,
colWidth
}: Props) => {
const { handlePopUpOpen, handlePopUpToggle, handlePopUpClose, popUp } = usePopUp([
"editSecret"
@@ -383,7 +385,10 @@ export const SecretItem = memo(
</>
)}
</div>
<div className="flex h-11 w-80 flex-shrink-0 items-center px-4 py-2">
<div
className="flex h-11 flex-shrink-0 items-center px-4 py-2"
style={{ width: colWidth }}
>
<Controller
name="key"
control={control}
@@ -51,6 +51,7 @@ type Props = {
isImported: boolean;
}[];
}[];
colWidth: number;
};
export const SecretListView = ({
@@ -62,7 +63,8 @@ export const SecretListView = ({
isVisible,
isProtectedBranch = false,
usedBySecretSyncs,
importedBy
importedBy,
colWidth
}: Props) => {
const queryClient = useQueryClient();
const { popUp, handlePopUpToggle, handlePopUpOpen, handlePopUpClose } = usePopUp([
@@ -554,6 +556,7 @@ export const SecretListView = ({
))}
{secrets.map((secret) => (
<SecretItem
colWidth={colWidth}
environment={environment}
secretPath={secretPath}
tags={wsTags}
@@ -1,6 +1,6 @@
import { GenericFieldLabel } from "@app/components/secret-syncs";
import { useRenderConnectionListServices } from "@app/hooks/api/appConnections/render";
import { TRenderSync } from "@app/hooks/api/secretSyncs/render-sync";
import { TRenderSync } from "@app/hooks/api/secretSyncs/types/render-sync";
type Props = {
secretSync: TRenderSync;
@@ -0,0 +1,21 @@
import { GenericFieldLabel } from "@app/components/secret-syncs";
import { Badge } from "@app/components/v2";
import { TRenderSync } from "@app/hooks/api/secretSyncs/types/render-sync";
type Props = {
secretSync: TRenderSync;
};
export const RenderSyncOptionsSection = ({ secretSync }: Props) => {
const {
syncOptions: { autoRedeployServices }
} = secretSync;
return (
<GenericFieldLabel label="Auto Redeploy Services">
<Badge variant={autoRedeployServices ? "success" : "danger"}>
{autoRedeployServices ? "Enabled" : "Disabled"}
</Badge>
</GenericFieldLabel>
);
};
@@ -13,6 +13,7 @@ import { SecretSync, TSecretSync } from "@app/hooks/api/secretSyncs";
import { AwsParameterStoreSyncOptionsSection } from "./AwsParameterStoreSyncOptionsSection";
import { AwsSecretsManagerSyncOptionsSection } from "./AwsSecretsManagerSyncOptionsSection";
import { RenderSyncOptionsSection } from "./RenderSyncOptionsSection";
type Props = {
secretSync: TSecretSync;
@@ -40,6 +41,9 @@ export const SecretSyncOptionsSection = ({ secretSync, onEditOptions }: Props) =
<AwsSecretsManagerSyncOptionsSection secretSync={secretSync} />
);
break;
case SecretSync.Render:
AdditionalSyncOptionsComponent = <RenderSyncOptionsSection secretSync={secretSync} />;
break;
case SecretSync.GitHub:
case SecretSync.GCPSecretManager:
case SecretSync.AzureKeyVault:
@@ -56,7 +60,6 @@ export const SecretSyncOptionsSection = ({ secretSync, onEditOptions }: Props) =
case SecretSync.OCIVault:
case SecretSync.OnePass:
case SecretSync.Heroku:
case SecretSync.Render:
case SecretSync.Flyio:
case SecretSync.GitLab:
case SecretSync.CloudflarePages:
@@ -1,12 +1,17 @@
import { useServerConfig } from "@app/context";
import { AutoCapitalizationSection } from "../AutoCapitalizationSection";
import { BackfillSecretReferenceSecretion } from "../BackfillSecretReferenceSection";
import { EnvironmentSection } from "../EnvironmentSection";
import { PointInTimeVersionLimitSection } from "../PointInTimeVersionLimitSection";
import { SecretDetectionIgnoreValuesSection } from "../SecretDetectionIgnoreValuesSection/SecretDetectionIgnoreValuesSection";
import { SecretSharingSection } from "../SecretSharingSection";
import { SecretSnapshotsLegacySection } from "../SecretSnapshotsLegacySection";
import { SecretTagsSection } from "../SecretTagsSection";
export const SecretSettingsTab = () => {
const { config } = useServerConfig();
return (
<div>
<EnvironmentSection />
@@ -15,6 +20,7 @@ export const SecretSettingsTab = () => {
<SecretSharingSection />
<SecretSnapshotsLegacySection />
<PointInTimeVersionLimitSection />
{config.paramsFolderSecretDetectionEnabled && <SecretDetectionIgnoreValuesSection />}
<BackfillSecretReferenceSecretion />
</div>
);
@@ -0,0 +1,144 @@
import { useEffect } from "react";
import { Controller, useFieldArray, useForm } from "react-hook-form";
import { faPlus, faTrash } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { zodResolver } from "@hookform/resolvers/zod";
import { z } from "zod";
import { createNotification } from "@app/components/notifications";
import { Button, FormControl, IconButton, Input } from "@app/components/v2";
import { useProjectPermission, useWorkspace } from "@app/context";
import { useUpdateProject } from "@app/hooks/api";
import { ProjectMembershipRole } from "@app/hooks/api/roles/types";
const formSchema = z.object({
ignoreValues: z
.object({
value: z.string().trim().min(1, "Secret value is required")
})
.array()
.default([])
});
type TForm = z.infer<typeof formSchema>;
export const SecretDetectionIgnoreValuesSection = () => {
const { currentWorkspace } = useWorkspace();
const { membership } = useProjectPermission();
const { mutateAsync: updateProject } = useUpdateProject();
const {
control,
formState: { isSubmitting, isDirty },
handleSubmit,
reset
} = useForm<TForm>({
resolver: zodResolver(formSchema),
defaultValues: {
ignoreValues: []
}
});
const ignoreValuesFormFields = useFieldArray({
control,
name: "ignoreValues"
});
useEffect(() => {
const existingIgnoreValues = currentWorkspace?.secretDetectionIgnoreValues || [];
reset({
ignoreValues:
existingIgnoreValues.length > 0
? existingIgnoreValues.map((value) => ({ value }))
: [{ value: "" }] // Show one empty field by default
});
}, [currentWorkspace?.secretDetectionIgnoreValues, reset]);
const handleIgnoreValuesSubmit = async ({ ignoreValues }: TForm) => {
try {
await updateProject({
projectID: currentWorkspace.id,
secretDetectionIgnoreValues: ignoreValues.map((item) => item.value)
});
createNotification({
text: "Successfully updated secret detection ignore values",
type: "success"
});
} catch {
createNotification({
text: "Failed updating secret detection ignore values",
type: "error"
});
}
};
const isAdmin = membership.roles.includes(ProjectMembershipRole.Admin);
if (!currentWorkspace) return null;
return (
<div className="mb-6 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
<div className="flex w-full items-center justify-between">
<p className="text-xl font-semibold">Secret Detection</p>
</div>
<p className="mb-4 mt-2 max-w-2xl text-sm text-gray-400">Define secret values to ignore when scanning designated parameter folders. Add values here to prevent false positives or allow approved sensitive data. These ignored values will not trigger policy violation alerts.</p>
<form onSubmit={handleSubmit(handleIgnoreValuesSubmit)} autoComplete="off">
<div className="mb-4">
<div className="flex flex-col space-y-2">
{ignoreValuesFormFields.fields.map(({ id: ignoreValueFieldId }, i) => (
<div key={ignoreValueFieldId} className="flex items-end space-x-2">
<div className="flex-grow">
{i === 0 && <span className="text-xs text-mineshaft-400">Secret Value</span>}
<Controller
control={control}
name={`ignoreValues.${i}.value`}
render={({ field, fieldState: { error } }) => (
<FormControl
isError={Boolean(error?.message)}
errorText={error?.message}
className="mb-0"
>
<Input {...field} placeholder="sk-1234567890abcdef" isDisabled={!isAdmin} />
</FormControl>
)}
/>
</div>
<IconButton
ariaLabel="delete ignore value"
className="bottom-0.5 h-9"
variant="outline_bg"
onClick={() => ignoreValuesFormFields.remove(i)}
isDisabled={!isAdmin}
>
<FontAwesomeIcon icon={faTrash} />
</IconButton>
</div>
))}
<div className="mt-2 flex justify-end">
<Button
leftIcon={<FontAwesomeIcon icon={faPlus} />}
size="xs"
variant="outline_bg"
onClick={() => ignoreValuesFormFields.append({ value: "" })}
isDisabled={!isAdmin}
>
Add value to ignore
</Button>
</div>
</div>
</div>
<Button
colorSchema="secondary"
type="submit"
isLoading={isSubmitting}
disabled={!isAdmin || !isDirty}
>
Save
</Button>
</form>
</div>
);
};
@@ -1,6 +1,6 @@
import { faBan } from "@fortawesome/free-solid-svg-icons";
import { ContentLoader, EmptyState } from "@app/components/v2";
import { AccessRestrictedBanner, ContentLoader, EmptyState } from "@app/components/v2";
import { useSubscription, useWorkspace } from "@app/context";
import { useGetSecretScanningConfig } from "@app/hooks/api/secretScanningV2";
@@ -16,16 +16,14 @@ export const ProjectScanningConfigTab = () => {
if (!subscription.secretScanning) {
return (
<div className="flex h-full w-full items-center justify-center px-20">
<EmptyState
className="rounded-md text-center"
icon={faBan}
title={
<span>
<div className="mt-60 flex h-full w-full items-center justify-center px-20">
<AccessRestrictedBanner
body={
<>
Your current plan doesn&apos;t support Secret Scanning.
<br /> Please contact Infisical Support or reach out through our Slack channel for
assistance.
</span>
</>
}
/>
</div>
+143
View File
@@ -81,10 +81,15 @@ import { Route as secretManagerIntegrationsRouteBitbucketOauthRedirectImport } f
import { Route as secretManagerIntegrationsRouteAzureKeyVaultOauthRedirectImport } from './pages/secret-manager/integrations/route-azure-key-vault-oauth-redirect'
import { Route as secretManagerIntegrationsRouteAzureAppConfigurationsOauthRedirectImport } from './pages/secret-manager/integrations/route-azure-app-configurations-oauth-redirect'
import { Route as organizationSettingsPageOauthCallbackPageRouteImport } from './pages/organization/SettingsPage/OauthCallbackPage/route'
import { Route as projectAuditLogsPageRouteSshImport } from './pages/project/AuditLogsPage/route-ssh'
import { Route as projectAccessControlPageRouteSshImport } from './pages/project/AccessControlPage/route-ssh'
import { Route as projectAuditLogsPageRouteSecretScanningImport } from './pages/project/AuditLogsPage/route-secret-scanning'
import { Route as projectAccessControlPageRouteSecretScanningImport } from './pages/project/AccessControlPage/route-secret-scanning'
import { Route as projectAuditLogsPageRouteSecretManagerImport } from './pages/project/AuditLogsPage/route-secret-manager'
import { Route as projectAccessControlPageRouteSecretManagerImport } from './pages/project/AccessControlPage/route-secret-manager'
import { Route as projectAuditLogsPageRouteKmsImport } from './pages/project/AuditLogsPage/route-kms'
import { Route as projectAccessControlPageRouteKmsImport } from './pages/project/AccessControlPage/route-kms'
import { Route as projectAuditLogsPageRouteCertManagerImport } from './pages/project/AuditLogsPage/route-cert-manager'
import { Route as projectAccessControlPageRouteCertManagerImport } from './pages/project/AccessControlPage/route-cert-manager'
import { Route as sshSettingsPageRouteImport } from './pages/ssh/SettingsPage/route'
import { Route as sshSshHostsPageRouteImport } from './pages/ssh/SshHostsPage/route'
@@ -903,6 +908,13 @@ const organizationSettingsPageOauthCallbackPageRouteRoute =
AuthenticateInjectOrgDetailsOrgLayoutOrganizationSettingsRoute,
} as any)
const projectAuditLogsPageRouteSshRoute =
projectAuditLogsPageRouteSshImport.update({
id: '/audit-logs',
path: '/audit-logs',
getParentRoute: () => sshLayoutRoute,
} as any)
const projectAccessControlPageRouteSshRoute =
projectAccessControlPageRouteSshImport.update({
id: '/access-management',
@@ -919,6 +931,13 @@ const AuthenticateInjectOrgDetailsOrgLayoutProjectsSecretScanningProjectIdSecret
} as any,
)
const projectAuditLogsPageRouteSecretScanningRoute =
projectAuditLogsPageRouteSecretScanningImport.update({
id: '/audit-logs',
path: '/audit-logs',
getParentRoute: () => secretScanningLayoutRoute,
} as any)
const projectAccessControlPageRouteSecretScanningRoute =
projectAccessControlPageRouteSecretScanningImport.update({
id: '/access-management',
@@ -935,6 +954,13 @@ const AuthenticateInjectOrgDetailsOrgLayoutProjectsSecretManagementProjectIdSecr
} as any,
)
const projectAuditLogsPageRouteSecretManagerRoute =
projectAuditLogsPageRouteSecretManagerImport.update({
id: '/audit-logs',
path: '/audit-logs',
getParentRoute: () => secretManagerLayoutRoute,
} as any)
const projectAccessControlPageRouteSecretManagerRoute =
projectAccessControlPageRouteSecretManagerImport.update({
id: '/access-management',
@@ -942,6 +968,13 @@ const projectAccessControlPageRouteSecretManagerRoute =
getParentRoute: () => secretManagerLayoutRoute,
} as any)
const projectAuditLogsPageRouteKmsRoute =
projectAuditLogsPageRouteKmsImport.update({
id: '/audit-logs',
path: '/audit-logs',
getParentRoute: () => kmsLayoutRoute,
} as any)
const projectAccessControlPageRouteKmsRoute =
projectAccessControlPageRouteKmsImport.update({
id: '/access-management',
@@ -967,6 +1000,13 @@ const AuthenticateInjectOrgDetailsOrgLayoutProjectsCertManagementProjectIdCertMa
} as any,
)
const projectAuditLogsPageRouteCertManagerRoute =
projectAuditLogsPageRouteCertManagerImport.update({
id: '/audit-logs',
path: '/audit-logs',
getParentRoute: () => certManagerLayoutRoute,
} as any)
const projectAccessControlPageRouteCertManagerRoute =
projectAccessControlPageRouteCertManagerImport.update({
id: '/access-management',
@@ -2722,6 +2762,13 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof projectAccessControlPageRouteCertManagerImport
parentRoute: typeof certManagerLayoutImport
}
'/_authenticate/_inject-org-details/_org-layout/projects/cert-management/$projectId/_cert-manager-layout/audit-logs': {
id: '/_authenticate/_inject-org-details/_org-layout/projects/cert-management/$projectId/_cert-manager-layout/audit-logs'
path: '/audit-logs'
fullPath: '/projects/cert-management/$projectId/audit-logs'
preLoaderRoute: typeof projectAuditLogsPageRouteCertManagerImport
parentRoute: typeof certManagerLayoutImport
}
'/_authenticate/_inject-org-details/_org-layout/projects/cert-management/$projectId/_cert-manager-layout/certificate-templates': {
id: '/_authenticate/_inject-org-details/_org-layout/projects/cert-management/$projectId/_cert-manager-layout/certificate-templates'
path: '/certificate-templates'
@@ -2743,6 +2790,13 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof projectAccessControlPageRouteKmsImport
parentRoute: typeof kmsLayoutImport
}
'/_authenticate/_inject-org-details/_org-layout/projects/kms/$projectId/_kms-layout/audit-logs': {
id: '/_authenticate/_inject-org-details/_org-layout/projects/kms/$projectId/_kms-layout/audit-logs'
path: '/audit-logs'
fullPath: '/projects/kms/$projectId/audit-logs'
preLoaderRoute: typeof projectAuditLogsPageRouteKmsImport
parentRoute: typeof kmsLayoutImport
}
'/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/access-management': {
id: '/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/access-management'
path: '/access-management'
@@ -2750,6 +2804,13 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof projectAccessControlPageRouteSecretManagerImport
parentRoute: typeof secretManagerLayoutImport
}
'/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/audit-logs': {
id: '/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/audit-logs'
path: '/audit-logs'
fullPath: '/projects/secret-management/$projectId/audit-logs'
preLoaderRoute: typeof projectAuditLogsPageRouteSecretManagerImport
parentRoute: typeof secretManagerLayoutImport
}
'/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/integrations': {
id: '/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/integrations'
path: '/integrations'
@@ -2764,6 +2825,13 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof projectAccessControlPageRouteSecretScanningImport
parentRoute: typeof secretScanningLayoutImport
}
'/_authenticate/_inject-org-details/_org-layout/projects/secret-scanning/$projectId/_secret-scanning-layout/audit-logs': {
id: '/_authenticate/_inject-org-details/_org-layout/projects/secret-scanning/$projectId/_secret-scanning-layout/audit-logs'
path: '/audit-logs'
fullPath: '/projects/secret-scanning/$projectId/audit-logs'
preLoaderRoute: typeof projectAuditLogsPageRouteSecretScanningImport
parentRoute: typeof secretScanningLayoutImport
}
'/_authenticate/_inject-org-details/_org-layout/projects/secret-scanning/$projectId/_secret-scanning-layout/data-sources': {
id: '/_authenticate/_inject-org-details/_org-layout/projects/secret-scanning/$projectId/_secret-scanning-layout/data-sources'
path: '/data-sources'
@@ -2778,6 +2846,13 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof projectAccessControlPageRouteSshImport
parentRoute: typeof sshLayoutImport
}
'/_authenticate/_inject-org-details/_org-layout/projects/ssh/$projectId/_ssh-layout/audit-logs': {
id: '/_authenticate/_inject-org-details/_org-layout/projects/ssh/$projectId/_ssh-layout/audit-logs'
path: '/audit-logs'
fullPath: '/projects/ssh/$projectId/audit-logs'
preLoaderRoute: typeof projectAuditLogsPageRouteSshImport
parentRoute: typeof sshLayoutImport
}
'/_authenticate/_inject-org-details/_org-layout/projects/cert-management/$projectId/_cert-manager-layout/certificate-templates/': {
id: '/_authenticate/_inject-org-details/_org-layout/projects/cert-management/$projectId/_cert-manager-layout/certificate-templates/'
path: '/'
@@ -3781,6 +3856,7 @@ interface certManagerLayoutRouteChildren {
certManagerCertificatesPageRouteRoute: typeof certManagerCertificatesPageRouteRoute
certManagerSettingsPageRouteRoute: typeof certManagerSettingsPageRouteRoute
projectAccessControlPageRouteCertManagerRoute: typeof projectAccessControlPageRouteCertManagerRoute
projectAuditLogsPageRouteCertManagerRoute: typeof projectAuditLogsPageRouteCertManagerRoute
AuthenticateInjectOrgDetailsOrgLayoutProjectsCertManagementProjectIdCertManagerLayoutCertificateTemplatesRoute: typeof AuthenticateInjectOrgDetailsOrgLayoutProjectsCertManagementProjectIdCertManagerLayoutCertificateTemplatesRouteWithChildren
AuthenticateInjectOrgDetailsOrgLayoutProjectsCertManagementProjectIdCertManagerLayoutSubscribersRoute: typeof AuthenticateInjectOrgDetailsOrgLayoutProjectsCertManagementProjectIdCertManagerLayoutSubscribersRouteWithChildren
certManagerCertAuthDetailsByIDPageRouteRoute: typeof certManagerCertAuthDetailsByIDPageRouteRoute
@@ -3799,6 +3875,8 @@ const certManagerLayoutRouteChildren: certManagerLayoutRouteChildren = {
certManagerSettingsPageRouteRoute: certManagerSettingsPageRouteRoute,
projectAccessControlPageRouteCertManagerRoute:
projectAccessControlPageRouteCertManagerRoute,
projectAuditLogsPageRouteCertManagerRoute:
projectAuditLogsPageRouteCertManagerRoute,
AuthenticateInjectOrgDetailsOrgLayoutProjectsCertManagementProjectIdCertManagerLayoutCertificateTemplatesRoute:
AuthenticateInjectOrgDetailsOrgLayoutProjectsCertManagementProjectIdCertManagerLayoutCertificateTemplatesRouteWithChildren,
AuthenticateInjectOrgDetailsOrgLayoutProjectsCertManagementProjectIdCertManagerLayoutSubscribersRoute:
@@ -3839,6 +3917,7 @@ interface kmsLayoutRouteChildren {
kmsOverviewPageRouteRoute: typeof kmsOverviewPageRouteRoute
kmsSettingsPageRouteRoute: typeof kmsSettingsPageRouteRoute
projectAccessControlPageRouteKmsRoute: typeof projectAccessControlPageRouteKmsRoute
projectAuditLogsPageRouteKmsRoute: typeof projectAuditLogsPageRouteKmsRoute
projectGroupDetailsByIDPageRouteKmsRoute: typeof projectGroupDetailsByIDPageRouteKmsRoute
projectIdentityDetailsByIDPageRouteKmsRoute: typeof projectIdentityDetailsByIDPageRouteKmsRoute
projectMemberDetailsByIDPageRouteKmsRoute: typeof projectMemberDetailsByIDPageRouteKmsRoute
@@ -3850,6 +3929,7 @@ const kmsLayoutRouteChildren: kmsLayoutRouteChildren = {
kmsOverviewPageRouteRoute: kmsOverviewPageRouteRoute,
kmsSettingsPageRouteRoute: kmsSettingsPageRouteRoute,
projectAccessControlPageRouteKmsRoute: projectAccessControlPageRouteKmsRoute,
projectAuditLogsPageRouteKmsRoute: projectAuditLogsPageRouteKmsRoute,
projectGroupDetailsByIDPageRouteKmsRoute:
projectGroupDetailsByIDPageRouteKmsRoute,
projectIdentityDetailsByIDPageRouteKmsRoute:
@@ -4166,6 +4246,7 @@ interface secretManagerLayoutRouteChildren {
secretManagerSecretRotationPageRouteRoute: typeof secretManagerSecretRotationPageRouteRoute
secretManagerSettingsPageRouteRoute: typeof secretManagerSettingsPageRouteRoute
projectAccessControlPageRouteSecretManagerRoute: typeof projectAccessControlPageRouteSecretManagerRoute
projectAuditLogsPageRouteSecretManagerRoute: typeof projectAuditLogsPageRouteSecretManagerRoute
AuthenticateInjectOrgDetailsOrgLayoutProjectsSecretManagementProjectIdSecretManagerLayoutIntegrationsRoute: typeof AuthenticateInjectOrgDetailsOrgLayoutProjectsSecretManagementProjectIdSecretManagerLayoutIntegrationsRouteWithChildren
secretManagerSecretDashboardPageRouteRoute: typeof secretManagerSecretDashboardPageRouteRoute
projectGroupDetailsByIDPageRouteSecretManagerRoute: typeof projectGroupDetailsByIDPageRouteSecretManagerRoute
@@ -4186,6 +4267,8 @@ const secretManagerLayoutRouteChildren: secretManagerLayoutRouteChildren = {
secretManagerSettingsPageRouteRoute: secretManagerSettingsPageRouteRoute,
projectAccessControlPageRouteSecretManagerRoute:
projectAccessControlPageRouteSecretManagerRoute,
projectAuditLogsPageRouteSecretManagerRoute:
projectAuditLogsPageRouteSecretManagerRoute,
AuthenticateInjectOrgDetailsOrgLayoutProjectsSecretManagementProjectIdSecretManagerLayoutIntegrationsRoute:
AuthenticateInjectOrgDetailsOrgLayoutProjectsSecretManagementProjectIdSecretManagerLayoutIntegrationsRouteWithChildren,
secretManagerSecretDashboardPageRouteRoute:
@@ -4241,6 +4324,7 @@ interface secretScanningLayoutRouteChildren {
secretScanningSecretScanningFindingsPageRouteRoute: typeof secretScanningSecretScanningFindingsPageRouteRoute
secretScanningSettingsPageRouteRoute: typeof secretScanningSettingsPageRouteRoute
projectAccessControlPageRouteSecretScanningRoute: typeof projectAccessControlPageRouteSecretScanningRoute
projectAuditLogsPageRouteSecretScanningRoute: typeof projectAuditLogsPageRouteSecretScanningRoute
AuthenticateInjectOrgDetailsOrgLayoutProjectsSecretScanningProjectIdSecretScanningLayoutDataSourcesRoute: typeof AuthenticateInjectOrgDetailsOrgLayoutProjectsSecretScanningProjectIdSecretScanningLayoutDataSourcesRouteWithChildren
projectGroupDetailsByIDPageRouteSecretScanningRoute: typeof projectGroupDetailsByIDPageRouteSecretScanningRoute
projectIdentityDetailsByIDPageRouteSecretScanningRoute: typeof projectIdentityDetailsByIDPageRouteSecretScanningRoute
@@ -4254,6 +4338,8 @@ const secretScanningLayoutRouteChildren: secretScanningLayoutRouteChildren = {
secretScanningSettingsPageRouteRoute: secretScanningSettingsPageRouteRoute,
projectAccessControlPageRouteSecretScanningRoute:
projectAccessControlPageRouteSecretScanningRoute,
projectAuditLogsPageRouteSecretScanningRoute:
projectAuditLogsPageRouteSecretScanningRoute,
AuthenticateInjectOrgDetailsOrgLayoutProjectsSecretScanningProjectIdSecretScanningLayoutDataSourcesRoute:
AuthenticateInjectOrgDetailsOrgLayoutProjectsSecretScanningProjectIdSecretScanningLayoutDataSourcesRouteWithChildren,
projectGroupDetailsByIDPageRouteSecretScanningRoute:
@@ -4289,6 +4375,7 @@ interface sshLayoutRouteChildren {
sshSshHostsPageRouteRoute: typeof sshSshHostsPageRouteRoute
sshSettingsPageRouteRoute: typeof sshSettingsPageRouteRoute
projectAccessControlPageRouteSshRoute: typeof projectAccessControlPageRouteSshRoute
projectAuditLogsPageRouteSshRoute: typeof projectAuditLogsPageRouteSshRoute
sshSshCaByIDPageRouteRoute: typeof sshSshCaByIDPageRouteRoute
sshSshHostGroupDetailsByIDPageRouteRoute: typeof sshSshHostGroupDetailsByIDPageRouteRoute
projectGroupDetailsByIDPageRouteSshRoute: typeof projectGroupDetailsByIDPageRouteSshRoute
@@ -4303,6 +4390,7 @@ const sshLayoutRouteChildren: sshLayoutRouteChildren = {
sshSshHostsPageRouteRoute: sshSshHostsPageRouteRoute,
sshSettingsPageRouteRoute: sshSettingsPageRouteRoute,
projectAccessControlPageRouteSshRoute: projectAccessControlPageRouteSshRoute,
projectAuditLogsPageRouteSshRoute: projectAuditLogsPageRouteSshRoute,
sshSshCaByIDPageRouteRoute: sshSshCaByIDPageRouteRoute,
sshSshHostGroupDetailsByIDPageRouteRoute:
sshSshHostGroupDetailsByIDPageRouteRoute,
@@ -4642,14 +4730,19 @@ export interface FileRoutesByFullPath {
'/projects/ssh/$projectId/overview': typeof sshSshHostsPageRouteRoute
'/projects/ssh/$projectId/settings': typeof sshSettingsPageRouteRoute
'/projects/cert-management/$projectId/access-management': typeof projectAccessControlPageRouteCertManagerRoute
'/projects/cert-management/$projectId/audit-logs': typeof projectAuditLogsPageRouteCertManagerRoute
'/projects/cert-management/$projectId/certificate-templates': typeof AuthenticateInjectOrgDetailsOrgLayoutProjectsCertManagementProjectIdCertManagerLayoutCertificateTemplatesRouteWithChildren
'/projects/cert-management/$projectId/subscribers': typeof AuthenticateInjectOrgDetailsOrgLayoutProjectsCertManagementProjectIdCertManagerLayoutSubscribersRouteWithChildren
'/projects/kms/$projectId/access-management': typeof projectAccessControlPageRouteKmsRoute
'/projects/kms/$projectId/audit-logs': typeof projectAuditLogsPageRouteKmsRoute
'/projects/secret-management/$projectId/access-management': typeof projectAccessControlPageRouteSecretManagerRoute
'/projects/secret-management/$projectId/audit-logs': typeof projectAuditLogsPageRouteSecretManagerRoute
'/projects/secret-management/$projectId/integrations': typeof AuthenticateInjectOrgDetailsOrgLayoutProjectsSecretManagementProjectIdSecretManagerLayoutIntegrationsRouteWithChildren
'/projects/secret-scanning/$projectId/access-management': typeof projectAccessControlPageRouteSecretScanningRoute
'/projects/secret-scanning/$projectId/audit-logs': typeof projectAuditLogsPageRouteSecretScanningRoute
'/projects/secret-scanning/$projectId/data-sources': typeof AuthenticateInjectOrgDetailsOrgLayoutProjectsSecretScanningProjectIdSecretScanningLayoutDataSourcesRouteWithChildren
'/projects/ssh/$projectId/access-management': typeof projectAccessControlPageRouteSshRoute
'/projects/ssh/$projectId/audit-logs': typeof projectAuditLogsPageRouteSshRoute
'/projects/cert-management/$projectId/certificate-templates/': typeof certManagerPkiTemplateListPageRouteRoute
'/projects/cert-management/$projectId/subscribers/': typeof certManagerPkiSubscribersPageRouteRoute
'/projects/secret-management/$projectId/integrations/': typeof secretManagerIntegrationsListPageRouteRoute
@@ -4852,10 +4945,15 @@ export interface FileRoutesByTo {
'/projects/ssh/$projectId/overview': typeof sshSshHostsPageRouteRoute
'/projects/ssh/$projectId/settings': typeof sshSettingsPageRouteRoute
'/projects/cert-management/$projectId/access-management': typeof projectAccessControlPageRouteCertManagerRoute
'/projects/cert-management/$projectId/audit-logs': typeof projectAuditLogsPageRouteCertManagerRoute
'/projects/kms/$projectId/access-management': typeof projectAccessControlPageRouteKmsRoute
'/projects/kms/$projectId/audit-logs': typeof projectAuditLogsPageRouteKmsRoute
'/projects/secret-management/$projectId/access-management': typeof projectAccessControlPageRouteSecretManagerRoute
'/projects/secret-management/$projectId/audit-logs': typeof projectAuditLogsPageRouteSecretManagerRoute
'/projects/secret-scanning/$projectId/access-management': typeof projectAccessControlPageRouteSecretScanningRoute
'/projects/secret-scanning/$projectId/audit-logs': typeof projectAuditLogsPageRouteSecretScanningRoute
'/projects/ssh/$projectId/access-management': typeof projectAccessControlPageRouteSshRoute
'/projects/ssh/$projectId/audit-logs': typeof projectAuditLogsPageRouteSshRoute
'/projects/cert-management/$projectId/certificate-templates': typeof certManagerPkiTemplateListPageRouteRoute
'/projects/cert-management/$projectId/subscribers': typeof certManagerPkiSubscribersPageRouteRoute
'/projects/secret-management/$projectId/integrations': typeof secretManagerIntegrationsListPageRouteRoute
@@ -5075,14 +5173,19 @@ export interface FileRoutesById {
'/_authenticate/_inject-org-details/_org-layout/projects/ssh/$projectId/_ssh-layout/overview': typeof sshSshHostsPageRouteRoute
'/_authenticate/_inject-org-details/_org-layout/projects/ssh/$projectId/_ssh-layout/settings': typeof sshSettingsPageRouteRoute
'/_authenticate/_inject-org-details/_org-layout/projects/cert-management/$projectId/_cert-manager-layout/access-management': typeof projectAccessControlPageRouteCertManagerRoute
'/_authenticate/_inject-org-details/_org-layout/projects/cert-management/$projectId/_cert-manager-layout/audit-logs': typeof projectAuditLogsPageRouteCertManagerRoute
'/_authenticate/_inject-org-details/_org-layout/projects/cert-management/$projectId/_cert-manager-layout/certificate-templates': typeof AuthenticateInjectOrgDetailsOrgLayoutProjectsCertManagementProjectIdCertManagerLayoutCertificateTemplatesRouteWithChildren
'/_authenticate/_inject-org-details/_org-layout/projects/cert-management/$projectId/_cert-manager-layout/subscribers': typeof AuthenticateInjectOrgDetailsOrgLayoutProjectsCertManagementProjectIdCertManagerLayoutSubscribersRouteWithChildren
'/_authenticate/_inject-org-details/_org-layout/projects/kms/$projectId/_kms-layout/access-management': typeof projectAccessControlPageRouteKmsRoute
'/_authenticate/_inject-org-details/_org-layout/projects/kms/$projectId/_kms-layout/audit-logs': typeof projectAuditLogsPageRouteKmsRoute
'/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/access-management': typeof projectAccessControlPageRouteSecretManagerRoute
'/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/audit-logs': typeof projectAuditLogsPageRouteSecretManagerRoute
'/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/integrations': typeof AuthenticateInjectOrgDetailsOrgLayoutProjectsSecretManagementProjectIdSecretManagerLayoutIntegrationsRouteWithChildren
'/_authenticate/_inject-org-details/_org-layout/projects/secret-scanning/$projectId/_secret-scanning-layout/access-management': typeof projectAccessControlPageRouteSecretScanningRoute
'/_authenticate/_inject-org-details/_org-layout/projects/secret-scanning/$projectId/_secret-scanning-layout/audit-logs': typeof projectAuditLogsPageRouteSecretScanningRoute
'/_authenticate/_inject-org-details/_org-layout/projects/secret-scanning/$projectId/_secret-scanning-layout/data-sources': typeof AuthenticateInjectOrgDetailsOrgLayoutProjectsSecretScanningProjectIdSecretScanningLayoutDataSourcesRouteWithChildren
'/_authenticate/_inject-org-details/_org-layout/projects/ssh/$projectId/_ssh-layout/access-management': typeof projectAccessControlPageRouteSshRoute
'/_authenticate/_inject-org-details/_org-layout/projects/ssh/$projectId/_ssh-layout/audit-logs': typeof projectAuditLogsPageRouteSshRoute
'/_authenticate/_inject-org-details/_org-layout/projects/cert-management/$projectId/_cert-manager-layout/certificate-templates/': typeof certManagerPkiTemplateListPageRouteRoute
'/_authenticate/_inject-org-details/_org-layout/projects/cert-management/$projectId/_cert-manager-layout/subscribers/': typeof certManagerPkiSubscribersPageRouteRoute
'/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/integrations/': typeof secretManagerIntegrationsListPageRouteRoute
@@ -5295,14 +5398,19 @@ export interface FileRouteTypes {
| '/projects/ssh/$projectId/overview'
| '/projects/ssh/$projectId/settings'
| '/projects/cert-management/$projectId/access-management'
| '/projects/cert-management/$projectId/audit-logs'
| '/projects/cert-management/$projectId/certificate-templates'
| '/projects/cert-management/$projectId/subscribers'
| '/projects/kms/$projectId/access-management'
| '/projects/kms/$projectId/audit-logs'
| '/projects/secret-management/$projectId/access-management'
| '/projects/secret-management/$projectId/audit-logs'
| '/projects/secret-management/$projectId/integrations'
| '/projects/secret-scanning/$projectId/access-management'
| '/projects/secret-scanning/$projectId/audit-logs'
| '/projects/secret-scanning/$projectId/data-sources'
| '/projects/ssh/$projectId/access-management'
| '/projects/ssh/$projectId/audit-logs'
| '/projects/cert-management/$projectId/certificate-templates/'
| '/projects/cert-management/$projectId/subscribers/'
| '/projects/secret-management/$projectId/integrations/'
@@ -5504,10 +5612,15 @@ export interface FileRouteTypes {
| '/projects/ssh/$projectId/overview'
| '/projects/ssh/$projectId/settings'
| '/projects/cert-management/$projectId/access-management'
| '/projects/cert-management/$projectId/audit-logs'
| '/projects/kms/$projectId/access-management'
| '/projects/kms/$projectId/audit-logs'
| '/projects/secret-management/$projectId/access-management'
| '/projects/secret-management/$projectId/audit-logs'
| '/projects/secret-scanning/$projectId/access-management'
| '/projects/secret-scanning/$projectId/audit-logs'
| '/projects/ssh/$projectId/access-management'
| '/projects/ssh/$projectId/audit-logs'
| '/projects/cert-management/$projectId/certificate-templates'
| '/projects/cert-management/$projectId/subscribers'
| '/projects/secret-management/$projectId/integrations'
@@ -5725,14 +5838,19 @@ export interface FileRouteTypes {
| '/_authenticate/_inject-org-details/_org-layout/projects/ssh/$projectId/_ssh-layout/overview'
| '/_authenticate/_inject-org-details/_org-layout/projects/ssh/$projectId/_ssh-layout/settings'
| '/_authenticate/_inject-org-details/_org-layout/projects/cert-management/$projectId/_cert-manager-layout/access-management'
| '/_authenticate/_inject-org-details/_org-layout/projects/cert-management/$projectId/_cert-manager-layout/audit-logs'
| '/_authenticate/_inject-org-details/_org-layout/projects/cert-management/$projectId/_cert-manager-layout/certificate-templates'
| '/_authenticate/_inject-org-details/_org-layout/projects/cert-management/$projectId/_cert-manager-layout/subscribers'
| '/_authenticate/_inject-org-details/_org-layout/projects/kms/$projectId/_kms-layout/access-management'
| '/_authenticate/_inject-org-details/_org-layout/projects/kms/$projectId/_kms-layout/audit-logs'
| '/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/access-management'
| '/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/audit-logs'
| '/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/integrations'
| '/_authenticate/_inject-org-details/_org-layout/projects/secret-scanning/$projectId/_secret-scanning-layout/access-management'
| '/_authenticate/_inject-org-details/_org-layout/projects/secret-scanning/$projectId/_secret-scanning-layout/audit-logs'
| '/_authenticate/_inject-org-details/_org-layout/projects/secret-scanning/$projectId/_secret-scanning-layout/data-sources'
| '/_authenticate/_inject-org-details/_org-layout/projects/ssh/$projectId/_ssh-layout/access-management'
| '/_authenticate/_inject-org-details/_org-layout/projects/ssh/$projectId/_ssh-layout/audit-logs'
| '/_authenticate/_inject-org-details/_org-layout/projects/cert-management/$projectId/_cert-manager-layout/certificate-templates/'
| '/_authenticate/_inject-org-details/_org-layout/projects/cert-management/$projectId/_cert-manager-layout/subscribers/'
| '/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/integrations/'
@@ -6331,6 +6449,7 @@ export const routeTree = rootRoute
"/_authenticate/_inject-org-details/_org-layout/projects/cert-management/$projectId/_cert-manager-layout/certificates",
"/_authenticate/_inject-org-details/_org-layout/projects/cert-management/$projectId/_cert-manager-layout/settings",
"/_authenticate/_inject-org-details/_org-layout/projects/cert-management/$projectId/_cert-manager-layout/access-management",
"/_authenticate/_inject-org-details/_org-layout/projects/cert-management/$projectId/_cert-manager-layout/audit-logs",
"/_authenticate/_inject-org-details/_org-layout/projects/cert-management/$projectId/_cert-manager-layout/certificate-templates",
"/_authenticate/_inject-org-details/_org-layout/projects/cert-management/$projectId/_cert-manager-layout/subscribers",
"/_authenticate/_inject-org-details/_org-layout/projects/cert-management/$projectId/_cert-manager-layout/ca/$caName",
@@ -6349,6 +6468,7 @@ export const routeTree = rootRoute
"/_authenticate/_inject-org-details/_org-layout/projects/kms/$projectId/_kms-layout/overview",
"/_authenticate/_inject-org-details/_org-layout/projects/kms/$projectId/_kms-layout/settings",
"/_authenticate/_inject-org-details/_org-layout/projects/kms/$projectId/_kms-layout/access-management",
"/_authenticate/_inject-org-details/_org-layout/projects/kms/$projectId/_kms-layout/audit-logs",
"/_authenticate/_inject-org-details/_org-layout/projects/kms/$projectId/_kms-layout/groups/$groupId",
"/_authenticate/_inject-org-details/_org-layout/projects/kms/$projectId/_kms-layout/identities/$identityId",
"/_authenticate/_inject-org-details/_org-layout/projects/kms/$projectId/_kms-layout/members/$membershipId",
@@ -6365,6 +6485,7 @@ export const routeTree = rootRoute
"/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/secret-rotation",
"/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/settings",
"/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/access-management",
"/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/audit-logs",
"/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/integrations",
"/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/secrets/$envSlug",
"/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/groups/$groupId",
@@ -6381,6 +6502,7 @@ export const routeTree = rootRoute
"/_authenticate/_inject-org-details/_org-layout/projects/secret-scanning/$projectId/_secret-scanning-layout/findings",
"/_authenticate/_inject-org-details/_org-layout/projects/secret-scanning/$projectId/_secret-scanning-layout/settings",
"/_authenticate/_inject-org-details/_org-layout/projects/secret-scanning/$projectId/_secret-scanning-layout/access-management",
"/_authenticate/_inject-org-details/_org-layout/projects/secret-scanning/$projectId/_secret-scanning-layout/audit-logs",
"/_authenticate/_inject-org-details/_org-layout/projects/secret-scanning/$projectId/_secret-scanning-layout/data-sources",
"/_authenticate/_inject-org-details/_org-layout/projects/secret-scanning/$projectId/_secret-scanning-layout/groups/$groupId",
"/_authenticate/_inject-org-details/_org-layout/projects/secret-scanning/$projectId/_secret-scanning-layout/identities/$identityId",
@@ -6397,6 +6519,7 @@ export const routeTree = rootRoute
"/_authenticate/_inject-org-details/_org-layout/projects/ssh/$projectId/_ssh-layout/overview",
"/_authenticate/_inject-org-details/_org-layout/projects/ssh/$projectId/_ssh-layout/settings",
"/_authenticate/_inject-org-details/_org-layout/projects/ssh/$projectId/_ssh-layout/access-management",
"/_authenticate/_inject-org-details/_org-layout/projects/ssh/$projectId/_ssh-layout/audit-logs",
"/_authenticate/_inject-org-details/_org-layout/projects/ssh/$projectId/_ssh-layout/ca/$caId",
"/_authenticate/_inject-org-details/_org-layout/projects/ssh/$projectId/_ssh-layout/ssh-host-groups/$sshHostGroupId",
"/_authenticate/_inject-org-details/_org-layout/projects/ssh/$projectId/_ssh-layout/groups/$groupId",
@@ -6485,6 +6608,10 @@ export const routeTree = rootRoute
"filePath": "project/AccessControlPage/route-cert-manager.tsx",
"parent": "/_authenticate/_inject-org-details/_org-layout/projects/cert-management/$projectId/_cert-manager-layout"
},
"/_authenticate/_inject-org-details/_org-layout/projects/cert-management/$projectId/_cert-manager-layout/audit-logs": {
"filePath": "project/AuditLogsPage/route-cert-manager.tsx",
"parent": "/_authenticate/_inject-org-details/_org-layout/projects/cert-management/$projectId/_cert-manager-layout"
},
"/_authenticate/_inject-org-details/_org-layout/projects/cert-management/$projectId/_cert-manager-layout/certificate-templates": {
"filePath": "",
"parent": "/_authenticate/_inject-org-details/_org-layout/projects/cert-management/$projectId/_cert-manager-layout",
@@ -6504,10 +6631,18 @@ export const routeTree = rootRoute
"filePath": "project/AccessControlPage/route-kms.tsx",
"parent": "/_authenticate/_inject-org-details/_org-layout/projects/kms/$projectId/_kms-layout"
},
"/_authenticate/_inject-org-details/_org-layout/projects/kms/$projectId/_kms-layout/audit-logs": {
"filePath": "project/AuditLogsPage/route-kms.tsx",
"parent": "/_authenticate/_inject-org-details/_org-layout/projects/kms/$projectId/_kms-layout"
},
"/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/access-management": {
"filePath": "project/AccessControlPage/route-secret-manager.tsx",
"parent": "/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout"
},
"/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/audit-logs": {
"filePath": "project/AuditLogsPage/route-secret-manager.tsx",
"parent": "/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout"
},
"/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/integrations": {
"filePath": "",
"parent": "/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout",
@@ -6596,6 +6731,10 @@ export const routeTree = rootRoute
"filePath": "project/AccessControlPage/route-secret-scanning.tsx",
"parent": "/_authenticate/_inject-org-details/_org-layout/projects/secret-scanning/$projectId/_secret-scanning-layout"
},
"/_authenticate/_inject-org-details/_org-layout/projects/secret-scanning/$projectId/_secret-scanning-layout/audit-logs": {
"filePath": "project/AuditLogsPage/route-secret-scanning.tsx",
"parent": "/_authenticate/_inject-org-details/_org-layout/projects/secret-scanning/$projectId/_secret-scanning-layout"
},
"/_authenticate/_inject-org-details/_org-layout/projects/secret-scanning/$projectId/_secret-scanning-layout/data-sources": {
"filePath": "",
"parent": "/_authenticate/_inject-org-details/_org-layout/projects/secret-scanning/$projectId/_secret-scanning-layout",
@@ -6608,6 +6747,10 @@ export const routeTree = rootRoute
"filePath": "project/AccessControlPage/route-ssh.tsx",
"parent": "/_authenticate/_inject-org-details/_org-layout/projects/ssh/$projectId/_ssh-layout"
},
"/_authenticate/_inject-org-details/_org-layout/projects/ssh/$projectId/_ssh-layout/audit-logs": {
"filePath": "project/AuditLogsPage/route-ssh.tsx",
"parent": "/_authenticate/_inject-org-details/_org-layout/projects/ssh/$projectId/_ssh-layout"
},
"/_authenticate/_inject-org-details/_org-layout/projects/cert-management/$projectId/_cert-manager-layout/certificate-templates/": {
"filePath": "cert-manager/PkiTemplateListPage/route.tsx",
"parent": "/_authenticate/_inject-org-details/_org-layout/projects/cert-management/$projectId/_cert-manager-layout/certificate-templates"
+5
View File
@@ -63,6 +63,7 @@ const secretManagerRoutes = route("/projects/secret-management/$projectId", [
)
])
]),
route("/audit-logs", "project/AuditLogsPage/route-secret-manager.tsx"),
route("/access-management", "project/AccessControlPage/route-secret-manager.tsx"),
route("/roles/$roleSlug", "project/RoleDetailsBySlugPage/route-secret-manager.tsx"),
route("/identities/$identityId", "project/IdentityDetailsByIDPage/route-secret-manager.tsx"),
@@ -312,6 +313,7 @@ const certManagerRoutes = route("/projects/cert-management/$projectId", [
route("/ca/$caName", "cert-manager/CertAuthDetailsByIDPage/route.tsx"),
route("/pki-collections/$collectionId", "cert-manager/PkiCollectionDetailsByIDPage/routes.tsx"),
route("/settings", "cert-manager/SettingsPage/route.tsx"),
route("/audit-logs", "project/AuditLogsPage/route-cert-manager.tsx"),
route("/access-management", "project/AccessControlPage/route-cert-manager.tsx"),
route("/roles/$roleSlug", "project/RoleDetailsBySlugPage/route-cert-manager.tsx"),
route("/identities/$identityId", "project/IdentityDetailsByIDPage/route-cert-manager.tsx"),
@@ -325,6 +327,7 @@ const kmsRoutes = route("/projects/kms/$projectId", [
route("/overview", "kms/OverviewPage/route.tsx"),
route("/kmip", "kms/KmipPage/route.tsx"),
route("/settings", "kms/SettingsPage/route.tsx"),
route("/audit-logs", "project/AuditLogsPage/route-kms.tsx"),
route("/access-management", "project/AccessControlPage/route-kms.tsx"),
route("/roles/$roleSlug", "project/RoleDetailsBySlugPage/route-kms.tsx"),
route("/identities/$identityId", "project/IdentityDetailsByIDPage/route-kms.tsx"),
@@ -341,6 +344,7 @@ const sshRoutes = route("/projects/ssh/$projectId", [
route("/ca/$caId", "ssh/SshCaByIDPage/route.tsx"),
route("/ssh-host-groups/$sshHostGroupId", "ssh/SshHostGroupDetailsByIDPage/route.tsx"),
route("/settings", "ssh/SettingsPage/route.tsx"),
route("/audit-logs", "project/AuditLogsPage/route-ssh.tsx"),
route("/access-management", "project/AccessControlPage/route-ssh.tsx"),
route("/roles/$roleSlug", "project/RoleDetailsBySlugPage/route-ssh.tsx"),
route("/identities/$identityId", "project/IdentityDetailsByIDPage/route-ssh.tsx"),
@@ -357,6 +361,7 @@ const secretScanningRoutes = route("/projects/secret-scanning/$projectId", [
]),
route("/findings", "secret-scanning/SecretScanningFindingsPage/route.tsx"),
route("/settings", "secret-scanning/SettingsPage/route.tsx"),
route("/audit-logs", "project/AuditLogsPage/route-secret-scanning.tsx"),
route("/access-management", "project/AccessControlPage/route-secret-scanning.tsx"),
route("/roles/$roleSlug", "project/RoleDetailsBySlugPage/route-secret-scanning.tsx"),
route("/identities/$identityId", "project/IdentityDetailsByIDPage/route-secret-scanning.tsx"),