Merge branch 'main' of https://github.com/Infisical/infisical into fix/copy-secret-audit-log

This commit is contained in:
Piyush Gupta
2025-11-26 18:50:49 +05:30
142 changed files with 2574 additions and 816 deletions
@@ -0,0 +1,80 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 24.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 120 60" width="120" height="60" style="enable-background:new 0 0 120 60;" xml:space="preserve">
<style type="text/css">
.st0{fill:#808285;}
.st1{fill:url(#SVGID_1_);}
.st2{fill:#005B99;}
.st3{enable-background:new ;}
.st4{fill:#77787B;}
</style>
<g>
<path class="st0" d="M34.1,42.6h9.4v-3.2C41.5,41.2,38.2,42.3,34.1,42.6L34.1,42.6z"/>
<path class="st0" d="M17.3,40.1v2.5h11.9c-2.4-0.2-5-0.6-7.6-1.2C20.1,41,18.7,40.6,17.3,40.1L17.3,40.1z"/>
<radialGradient id="SVGID_1_" cx="-183.5882" cy="811.1324" r="47.498" gradientTransform="matrix(1 0 0 1 241.0864 -776.3726)" gradientUnits="userSpaceOnUse">
<stop offset="0" style="stop-color:#0095DA"/>
<stop offset="0.21" style="stop-color:#0095DA"/>
<stop offset="0.33" style="stop-color:#00ACE4"/>
<stop offset="0.9045" style="stop-color:#005093"/>
<stop offset="0.9335" style="stop-color:#005396"/>
<stop offset="0.954" style="stop-color:#005C9F"/>
<stop offset="0.9718" style="stop-color:#006BAE"/>
<stop offset="0.988" style="stop-color:#0080C4"/>
<stop offset="1" style="stop-color:#0095DA"/>
</radialGradient>
<path class="st1" d="M43.5,18.5H29.2C23,17.1,15.4,17.1,10,18.2c2.4-0.3,5.1-0.3,7.8,0.1c0.7,0.1,1.3,0.2,2,0.3l0,0
c2.9,0.5,5.7,1.2,8.1,2.2l0,0c0.3,0.1,0.5,0.2,0.8,0.3h0c0.1,0,0.2,0.1,0.3,0.1h0c0.1,0,0.2,0.1,0.3,0.1h0c0.1,0,0.2,0.1,0.2,0.1
l0,0c0.1,0,0.1,0.1,0.2,0.1l0,0c0.1,0,0.1,0.1,0.2,0.1l0,0c0.1,0,0.2,0.1,0.2,0.1l0,0l0,0h0c0.1,0,0.1,0.1,0.2,0.1l0,0
c0.1,0,0.1,0.1,0.2,0.1l0,0c0.1,0,0.1,0.1,0.2,0.1l0,0c0.1,0,0.1,0.1,0.2,0.1l0,0c0.1,0,0.1,0.1,0.2,0.1l0,0c0.1,0,0.1,0.1,0.2,0.1
l0.1,0c0.1,0,0.1,0.1,0.2,0.1l0.1,0c0.1,0,0.1,0.1,0.2,0.1l0.1,0c0.1,0,0.1,0.1,0.2,0.1l0.1,0c0.1,0,0.1,0.1,0.1,0.1l0.1,0.1
c0.1,0,0.1,0.1,0.1,0.1l0.1,0.1c0.1,0,0.1,0.1,0.1,0.1l0.1,0.1c0,0,0.1,0.1,0.1,0.1c3.2,2.2,5.1,4.9,5.1,7.6c0,3.1-2.6,5.7-6.8,7.2
c2.9-1.5,4.6-3.6,4.6-6.1c0-3.1-2.7-6.3-7-8.7c-0.1,0-0.1-0.1-0.2-0.1c-0.1,0-0.1-0.1-0.2-0.1c-0.1,0-0.1-0.1-0.2-0.1
c-0.1,0-0.1-0.1-0.2-0.1c-0.1,0-0.1-0.1-0.2-0.1c-0.1,0-0.1-0.1-0.2-0.1c-0.1,0-0.1-0.1-0.2-0.1l-0.2-0.1c-0.1,0-0.2-0.1-0.2-0.1
l-0.1-0.1c-0.1,0-0.1-0.1-0.2-0.1c-0.1,0-0.1-0.1-0.2-0.1c-0.1,0-0.1-0.1-0.2-0.1c-0.1,0-0.1-0.1-0.2-0.1c-0.1,0-0.1-0.1-0.2-0.1
L26,22c-0.1,0-0.2-0.1-0.3-0.1h0l-0.2-0.1l0,0c-0.1,0-0.2-0.1-0.3-0.1l-0.1,0c-0.1-0.1-0.2-0.1-0.4-0.1h0c-0.1,0-0.2-0.1-0.3-0.1h0
c-0.1-0.1-0.3-0.1-0.5-0.2l-0.1,0c-0.1-0.1-0.3-0.1-0.5-0.1h0c-0.3-0.1-0.5-0.2-0.8-0.3l0,0c-0.3-0.1-0.6-0.2-0.8-0.3l0,0
c-0.2-0.1-0.3-0.1-0.5-0.1l0,0c-0.7-0.2-1.5-0.4-2.3-0.5l0,0c-0.6-0.1-1.2-0.2-1.8-0.3v19.2c0.2,0.1,0.4,0.1,0.6,0.1
C30.3,42,41,39.4,41.8,33.2c0.4-3.3-2-6.9-6.1-10c3.3,1.9,6,4.1,7.8,6.4c1,1.4,1.7,2.7,2,4.1c-0.1-1.9-0.9-4.8-2-6.9L43.5,18.5
L43.5,18.5z"/>
<g>
<path class="st2" d="M67.5,26.9c0,1-0.2,2-0.7,2.9c-0.4,0.9-1,1.7-1.8,2.3c-0.8,0.7-1.7,1.2-2.8,1.6c-1.1,0.4-2.2,0.6-3.5,0.6H49
v-9h4.5v5.3h5.2c0.6,0,1.2-0.1,1.7-0.3c0.5-0.2,1-0.4,1.4-0.7c0.4-0.3,0.7-0.7,0.9-1.1c0.2-0.4,0.3-0.9,0.3-1.4
c0-0.5-0.1-1-0.3-1.4c-0.2-0.4-0.5-0.8-0.9-1.1c-0.4-0.3-0.8-0.6-1.4-0.7c-0.5-0.2-1.1-0.3-1.7-0.3H49l2.9-3.8h6.8
c1.3,0,2.4,0.2,3.5,0.5c1.1,0.3,2,0.8,2.8,1.5s1.4,1.4,1.8,2.3C67.2,24.9,67.5,25.8,67.5,26.9z"/>
<g>
<path class="st2" d="M82.8,21.4v6.8l-8.9-8c-0.4-0.3-0.7-0.5-1-0.6c-0.3-0.1-0.6-0.1-0.8-0.1c-0.3,0-0.6,0.1-0.9,0.1
c-0.3,0.1-0.6,0.3-0.8,0.5c-0.2,0.2-0.4,0.5-0.5,0.8c-0.1,0.3-0.2,0.8-0.2,1.2v12h4.1v-8.5l8.9,8c0.3,0.3,0.7,0.5,0.9,0.6
c0.3,0.1,0.6,0.1,0.8,0.1c0.3,0,0.6-0.1,0.9-0.1c0.3-0.1,0.6-0.3,0.8-0.5c0.2-0.2,0.4-0.5,0.5-0.8c0.1-0.3,0.2-0.8,0.2-1.2V19.9
L82.8,21.4z"/>
</g>
<path class="st2" d="M103,25.5c1.8,0,3.1,0.3,4,1c0.9,0.7,1.4,1.6,1.4,3c0,0.7-0.1,1.4-0.3,2c-0.2,0.6-0.6,1.1-1.1,1.5
c-0.5,0.4-1.2,0.7-1.9,0.9c-0.8,0.2-1.7,0.3-2.8,0.3H88.7l2.9-3.7h11c0.5,0,0.9-0.1,1.2-0.3c0.3-0.2,0.4-0.4,0.4-0.8
s-0.1-0.7-0.4-0.8c-0.3-0.2-0.6-0.2-1.2-0.2h-7.9c-0.9,0-1.8-0.1-2.4-0.3c-0.7-0.2-1.2-0.5-1.7-0.8c-0.5-0.4-0.8-0.8-1-1.3
c-0.2-0.5-0.3-1.1-0.3-1.7c0-0.7,0.1-1.3,0.4-1.9c0.2-0.6,0.6-1,1.1-1.4c0.5-0.4,1.1-0.7,1.9-0.9c0.8-0.2,1.7-0.3,2.8-0.3h12.6
l-2.9,3.8H95.1c-0.5,0-0.9,0.1-1.2,0.2c-0.3,0.1-0.4,0.4-0.4,0.8c0,0.4,0.1,0.6,0.4,0.8c0.3,0.1,0.6,0.2,1.2,0.2L103,25.5
L103,25.5z"/>
</g>
<g class="st3">
<path class="st4" d="M57.5,36.6v5h-1.4v-2.7v-0.7l0.1-0.4v-0.4h-0.1l-0.2,0.3l-0.2,0.3l-0.4,0.7l-1.7,2.8h-1.3l-1.7-2.8l-0.4-0.7
l-0.2-0.3l-0.2-0.3h-0.1l0,0.4v0.4l0.1,0.7v2.7h-1.5v-5h2.4l1.4,2.3l0.4,0.7l0.2,0.3l0.2,0.3H53l0.2-0.3l0.2-0.3l0.4-0.7l1.4-2.3
L57.5,36.6L57.5,36.6z"/>
<path class="st4" d="M63.6,40.6h-3.3l-0.5,0.9h-1.6l2.6-5H63l2.6,5H64L63.6,40.6z M63.2,39.9l-1.3-2.6l-1.3,2.6H63.2z"/>
<path class="st4" d="M66.2,41.6v-5H70c1,0,1.8,0.2,2.2,0.5s0.7,0.8,0.7,1.6c0,0.7,0,1.2-0.1,1.5c0,0.3-0.2,0.6-0.5,0.9
c-0.3,0.3-1,0.5-2.3,0.5H66.2L66.2,41.6z M67.7,40.7h2.1c0.7,0,1.2-0.1,1.4-0.3s0.3-0.7,0.3-1.4c0-0.7-0.1-1.2-0.3-1.4
s-0.6-0.3-1.3-0.3h-2.2L67.7,40.7L67.7,40.7z"/>
<path class="st4" d="M75.3,37.4v1.3h3.6v0.7h-3.6v1.4h3.8v0.8h-5.3v-5h5.3v0.8L75.3,37.4L75.3,37.4z"/>
<path class="st4" d="M84.4,37.4v1.3H88v0.7h-3.6v1.4h3.8v0.8H83v-5h5.3v0.8L84.4,37.4L84.4,37.4z"/>
<path class="st4" d="M94,40.6h-3.3l-0.5,0.9h-1.6l2.6-5h2.2l2.6,5h-1.5L94,40.6z M93.7,39.9l-1.3-2.6L91,39.9H93.7z"/>
<path class="st4" d="M102.4,38.1H101v-0.1c0-0.3-0.1-0.4-0.3-0.5c-0.2-0.1-0.6-0.1-1.2-0.1c-0.7,0-1.2,0-1.4,0.1
c-0.2,0.1-0.3,0.3-0.3,0.5c0,0.3,0.1,0.5,0.3,0.6s0.8,0.1,1.7,0.1c1.1,0,1.9,0.1,2.2,0.3c0.3,0.2,0.5,0.5,0.5,1.1
c0,0.7-0.2,1.1-0.6,1.3s-1.2,0.3-2.5,0.3c-1.3,0-2.2-0.1-2.5-0.3c-0.4-0.2-0.6-0.6-0.6-1.2V40h1.4v0.1c0,0.3,0.1,0.5,0.3,0.6
c0.2,0.1,0.7,0.1,1.5,0.1c0.7,0,1.1,0,1.2-0.1s0.3-0.3,0.3-0.6c0-0.3-0.1-0.4-0.2-0.5c-0.1-0.1-0.4-0.1-0.9-0.1l-0.8,0
c-1.2,0-2-0.1-2.3-0.3c-0.4-0.2-0.5-0.6-0.5-1.1c0-0.6,0.2-1,0.6-1.2c0.4-0.2,1.2-0.3,2.5-0.3c1.1,0,1.9,0.1,2.3,0.3
c0.4,0.2,0.6,0.5,0.6,1L102.4,38.1L102.4,38.1z"/>
<path class="st4" d="M110,36.6l-2.9,3.1v1.9h-1.5v-1.9l-2.8-3.1h1.7l1.2,1.3l0.3,0.4l0.2,0.2l0.2,0.2l0.2-0.2l0.2-0.2l0.3-0.4
l1.2-1.3H110z"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 6.2 KiB

+1 -1
View File
@@ -289,7 +289,7 @@
}
},
"project": {
"title": "Settings",
"title": "Project Settings",
"description": "These settings only apply to the currently selected Project.",
"danger-zone": "Danger Zone",
"delete-project": "Delete Project",
@@ -87,17 +87,24 @@ const shouldShowConditionalAccess = (
folderPath: string,
conditionalFields: string[]
): boolean => {
return actionRuleMap.some((rule) => {
// Find all rules that apply to this environment/path
const applicableRules = actionRuleMap.filter((rule) => {
const ruleConditions = rule[action]?.conditions;
if (!ruleConditions) return false;
// Check if any of the conditional fields are present
const hasConditionalField = conditionalFields.some((field) => ruleConditions[field]);
if (!hasConditionalField) return false;
// Check if base conditions (environment and secretPath) apply
return doBaseConditionsApply(ruleConditions, environment, folderPath);
});
// If no rules apply, don't show conditional
if (applicableRules.length === 0) return false;
// Check if ALL applicable rules have conditional fields and if at least one rule applies without conditional fields, show full access
const allRulesHaveConditionalFields = applicableRules.every((rule) => {
const ruleConditions = rule[action]?.conditions;
if (!ruleConditions) return false;
return conditionalFields.some((field) => ruleConditions[field]);
});
return allRulesHaveConditionalFields;
};
const determineAccessLevel = (
@@ -3,14 +3,7 @@ import { ReactNode } from "@tanstack/react-router";
import { LucideIcon } from "lucide-react";
import { twMerge } from "tailwind-merge";
import {
Badge,
InstanceIcon,
OrgIcon,
ProjectIcon,
SubOrgIcon,
TBadgeProps
} from "@app/components/v3";
import { InstanceIcon, OrgIcon, ProjectIcon, SubOrgIcon } from "@app/components/v3";
import { ProjectType } from "@app/hooks/api/projects/types";
type Props = {
@@ -21,41 +14,40 @@ type Props = {
scope: "org" | "namespace" | "instance" | ProjectType | null;
};
const SCOPE_NAME: Record<NonNullable<Props["scope"]>, { label: string; icon: LucideIcon }> = {
org: { label: "Organization", icon: OrgIcon },
[ProjectType.SecretManager]: { label: "Project", icon: ProjectIcon },
[ProjectType.CertificateManager]: { label: "Project", icon: ProjectIcon },
[ProjectType.SSH]: { label: "Project", icon: ProjectIcon },
[ProjectType.KMS]: { label: "Project", icon: ProjectIcon },
[ProjectType.PAM]: { label: "Project", icon: ProjectIcon },
[ProjectType.SecretScanning]: { label: "Project", icon: ProjectIcon },
namespace: { label: "Sub-Organization", icon: SubOrgIcon },
instance: { label: "Server", icon: InstanceIcon }
};
const SCOPE_VARIANT: Record<NonNullable<Props["scope"]>, TBadgeProps["variant"]> = {
org: "org",
[ProjectType.SecretManager]: "project",
[ProjectType.CertificateManager]: "project",
[ProjectType.SSH]: "project",
[ProjectType.KMS]: "project",
[ProjectType.PAM]: "project",
[ProjectType.SecretScanning]: "project",
namespace: "sub-org",
instance: "neutral"
const SCOPE_BADGE: Record<NonNullable<Props["scope"]>, { icon: LucideIcon; className: string }> = {
org: { className: "text-org", icon: OrgIcon },
[ProjectType.SecretManager]: { className: "text-project", icon: ProjectIcon },
[ProjectType.CertificateManager]: { className: "text-project", icon: ProjectIcon },
[ProjectType.SSH]: { className: "text-project", icon: ProjectIcon },
[ProjectType.KMS]: { className: "text-project", icon: ProjectIcon },
[ProjectType.PAM]: { className: "text-project", icon: ProjectIcon },
[ProjectType.SecretScanning]: { className: "text-project", icon: ProjectIcon },
namespace: { className: "text-sub-org", icon: SubOrgIcon },
instance: { className: "text-neutral", icon: InstanceIcon }
};
export const PageHeader = ({ title, description, children, className, scope }: Props) => (
<div className={twMerge("mb-10 w-full", className)}>
<div className="flex w-full justify-between">
<div className="mr-4 flex w-full items-center">
<h1 className="text-3xl font-medium text-white capitalize">{title}</h1>
{scope && (
<Badge variant={SCOPE_VARIANT[scope]} className="mt-1 ml-2.5">
{createElement(SCOPE_NAME[scope].icon)}
{SCOPE_NAME[scope].label}
</Badge>
)}
<h1
className={twMerge(
"text-3xl font-medium text-white capitalize underline decoration-2 underline-offset-4",
scope === "org" && "decoration-org/90",
scope === "instance" && "decoration-neutral/90",
scope === "namespace" && "decoration-sub-org/90",
Object.values(ProjectType).includes((scope as ProjectType) ?? "") &&
"decoration-project/90",
!scope && "no-underline"
)}
>
{scope &&
createElement(SCOPE_BADGE[scope].icon, {
size: 26,
className: twMerge(SCOPE_BADGE[scope].className, "mr-3 mb-1 inline-block")
})}
{title}
</h1>
</div>
<div className="flex items-center gap-2">{children}</div>
</div>
+2 -2
View File
@@ -47,8 +47,8 @@ export const Tab = ({
}) => (
<TabsPrimitive.Trigger
className={twMerge(
"flex h-10 cursor-pointer items-center justify-center border-transparent",
"px-3 text-sm font-medium whitespace-nowrap text-mineshaft-400 transition-all select-none",
"flex h-11 cursor-pointer items-center justify-center border-transparent",
"px-3 text-sm font-medium whitespace-nowrap text-mineshaft-300/75 transition-all select-none",
"data-[orientation=vertical]:xl:h-5 data-[orientation=vertical]:xl:border-b-0 data-[orientation=vertical]:xl:border-l",
"border-b hover:text-mineshaft-200",
"data-[state=active]:border-mineshaft-400 data-[state=active]:text-white",
+4
View File
@@ -49,6 +49,7 @@ import { BitbucketConnectionMethod } from "@app/hooks/api/appConnections/types/b
import { ChecklyConnectionMethod } from "@app/hooks/api/appConnections/types/checkly-connection";
import { ChefConnectionMethod } from "@app/hooks/api/appConnections/types/chef-connection";
import { DigitalOceanConnectionMethod } from "@app/hooks/api/appConnections/types/digital-ocean";
import { DNSMadeEasyConnectionMethod } from "@app/hooks/api/appConnections/types/dns-made-easy-connection";
import { HerokuConnectionMethod } from "@app/hooks/api/appConnections/types/heroku-connection";
import { LaravelForgeConnectionMethod } from "@app/hooks/api/appConnections/types/laravel-forge-connection";
import { NetlifyConnectionMethod } from "@app/hooks/api/appConnections/types/netlify-connection";
@@ -111,6 +112,7 @@ export const APP_CONNECTION_MAP: Record<
[AppConnection.Flyio]: { name: "Fly.io", image: "Flyio.svg" },
[AppConnection.GitLab]: { name: "GitLab", image: "GitLab.png" },
[AppConnection.Cloudflare]: { name: "Cloudflare", image: "Cloudflare.png" },
[AppConnection.DNSMadeEasy]: { name: "DNS Made Easy", image: "DNSMadeEasy.svg", size: 120 },
[AppConnection.Zabbix]: { name: "Zabbix", image: "Zabbix.png" },
[AppConnection.Railway]: { name: "Railway", image: "Railway.png" },
[AppConnection.Bitbucket]: { name: "Bitbucket", image: "Bitbucket.png" },
@@ -214,6 +216,8 @@ export const getAppConnectionMethodDetails = (method: TAppConnection["method"])
return { name: "Client Secret", icon: faKey };
case AzureClientSecretsConnectionMethod.Certificate:
return { name: "Certificate", icon: faCertificate };
case DNSMadeEasyConnectionMethod.APIKeySecret:
return { name: "API Key & Secret", icon: faKey };
default:
throw new Error(`Unhandled App Connection Method: ${method}`);
}
@@ -0,0 +1,2 @@
export * from "./queries";
export * from "./types";
@@ -0,0 +1,37 @@
import { useQuery, UseQueryOptions } from "@tanstack/react-query";
import { apiRequest } from "@app/config/request";
import { appConnectionKeys } from "../queries";
import { TDNSMadeEasyZone } from "./types";
const dnsMadeEasyConnectionKeys = {
all: [...appConnectionKeys.all, "dns-made-easy"] as const,
listZones: (connectionId: string) =>
[...dnsMadeEasyConnectionKeys.all, "zones", connectionId] as const
};
export const useDNSMadeEasyConnectionListZones = (
connectionId: string,
options?: Omit<
UseQueryOptions<
TDNSMadeEasyZone[],
unknown,
TDNSMadeEasyZone[],
ReturnType<typeof dnsMadeEasyConnectionKeys.listZones>
>,
"queryKey" | "queryFn"
>
) => {
return useQuery({
queryKey: dnsMadeEasyConnectionKeys.listZones(connectionId),
queryFn: async () => {
const { data } = await apiRequest.get<TDNSMadeEasyZone[]>(
`/api/v1/app-connections/dns-made-easy/${connectionId}/dns-made-easy-zones`
);
return data;
},
...options
});
};
@@ -0,0 +1,4 @@
export type TDNSMadeEasyZone = {
id: string;
name: string;
};
@@ -29,6 +29,7 @@ export enum AppConnection {
Flyio = "flyio",
GitLab = "gitlab",
Cloudflare = "cloudflare",
DNSMadeEasy = "dns-made-easy",
Bitbucket = "bitbucket",
Zabbix = "zabbix",
Railway = "railway",
@@ -184,6 +184,10 @@ export type TRedisConnectionOption = TAppConnectionOptionBase & {
app: AppConnection.Redis;
};
export type TDNSMadeEasyConnectionOption = TAppConnectionOptionBase & {
app: AppConnection.DNSMadeEasy;
};
export type TAppConnectionOption =
| TAwsConnectionOption
| TGitHubConnectionOption
@@ -225,7 +229,8 @@ export type TAppConnectionOption =
| TOktaConnectionOption
| TAzureAdCsConnectionOption
| TLaravelForgeConnectionOption
| TChefConnectionOption;
| TChefConnectionOption
| TDNSMadeEasyConnectionOption;
export type TAppConnectionOptionMap = {
[AppConnection.AWS]: TAwsConnectionOption;
@@ -257,6 +262,7 @@ export type TAppConnectionOptionMap = {
[AppConnection.Flyio]: TFlyioConnectionOption;
[AppConnection.GitLab]: TGitlabConnectionOption;
[AppConnection.Cloudflare]: TCloudflareConnectionOption;
[AppConnection.DNSMadeEasy]: TDNSMadeEasyConnectionOption;
[AppConnection.Bitbucket]: TBitbucketConnectionOption;
[AppConnection.Zabbix]: TZabbixConnectionOption;
[AppConnection.Railway]: TRailwayConnectionOption;
@@ -0,0 +1,14 @@
import { AppConnection } from "@app/hooks/api/appConnections/enums";
import { TRootAppConnection } from "@app/hooks/api/appConnections/types/root-connection";
export enum DNSMadeEasyConnectionMethod {
APIKeySecret = "api-key-secret"
}
export type TDNSMadeEasyConnection = TRootAppConnection & { app: AppConnection.DNSMadeEasy } & {
method: DNSMadeEasyConnectionMethod.APIKeySecret;
credentials: {
apiKey: string;
secretKey: string;
};
};
@@ -15,6 +15,7 @@ import { TChefConnection } from "./chef-connection";
import { TCloudflareConnection } from "./cloudflare-connection";
import { TDatabricksConnection } from "./databricks-connection";
import { TDigitalOceanConnection } from "./digital-ocean";
import { TDNSMadeEasyConnection } from "./dns-made-easy-connection";
import { TFlyioConnection } from "./flyio-connection";
import { TGcpConnection } from "./gcp-connection";
import { TGitHubConnection } from "./github-connection";
@@ -57,6 +58,7 @@ export * from "./checkly-connection";
export * from "./chef-connection";
export * from "./cloudflare-connection";
export * from "./databricks-connection";
export * from "./dns-made-easy-connection";
export * from "./flyio-connection";
export * from "./gcp-connection";
export * from "./github-connection";
@@ -127,7 +129,8 @@ export type TAppConnection =
| TNorthflankConnection
| TOktaConnection
| TRedisConnection
| TChefConnection;
| TChefConnection
| TDNSMadeEasyConnection;
export type TAvailableAppConnection = Pick<TAppConnection, "name" | "id" | "projectId">;
+4 -2
View File
@@ -16,12 +16,14 @@ export const caStatusToNameMap: { [K in CaStatus]: string } = {
export const ACME_DNS_PROVIDER_NAME_MAP: Record<AcmeDnsProvider, string> = {
[AcmeDnsProvider.ROUTE53]: "Route53",
[AcmeDnsProvider.Cloudflare]: "Cloudflare"
[AcmeDnsProvider.Cloudflare]: "Cloudflare",
[AcmeDnsProvider.DNSMadeEasy]: "DNS Made Easy"
};
export const ACME_DNS_PROVIDER_APP_CONNECTION_MAP: Record<AcmeDnsProvider, AppConnection> = {
[AcmeDnsProvider.ROUTE53]: AppConnection.AWS,
[AcmeDnsProvider.Cloudflare]: AppConnection.Cloudflare
[AcmeDnsProvider.Cloudflare]: AppConnection.Cloudflare,
[AcmeDnsProvider.DNSMadeEasy]: AppConnection.DNSMadeEasy
};
export const CA_TYPE_CAPABILITIES_MAP: Record<CaType, CaCapability[]> = {
+2 -1
View File
@@ -21,7 +21,8 @@ export enum CaRenewalType {
export enum AcmeDnsProvider {
ROUTE53 = "route53",
Cloudflare = "cloudflare"
Cloudflare = "cloudflare",
DNSMadeEasy = "dns-made-easy"
}
export enum CaCapability {
-4
View File
@@ -1,9 +1,5 @@
@import "tailwindcss";
@import "@fontsource/inter/400.css" layer(base);
@import "@fontsource/inter/500.css" layer(base);
@import "@fontsource/inter/700.css" layer(base);
@source not "../public";
/*
+2 -2
View File
@@ -14,8 +14,8 @@ export const KmsLayout = () => {
const location = useLocation();
return (
<div className="dark flex h-full w-full flex-col overflow-x-hidden">
<div className="border-b border-mineshaft-600 bg-mineshaft-900">
<div className="dark flex h-full w-full flex-col overflow-x-hidden bg-mineshaft-900">
<div className="border-y border-t-project/10 border-b-project/5 bg-gradient-to-b from-project/[0.075] to-project/[0.025] px-4 pt-0.5">
<motion.div
key="menu-project-items"
initial={{ x: -150 }}
@@ -22,8 +22,8 @@ import {
} from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { useQuery, useQueryClient } from "@tanstack/react-query";
import { Link, useLocation, useNavigate, useRouter, useRouterState } from "@tanstack/react-router";
import { UserPlusIcon } from "lucide-react";
import { Link, useLocation, useNavigate, useRouter } from "@tanstack/react-router";
import { ChevronRight, UserPlusIcon } from "lucide-react";
import { twMerge } from "tailwind-merge";
import { Mfa } from "@app/components/auth/Mfa";
@@ -31,7 +31,6 @@ import { createNotification } from "@app/components/notifications";
import { OrgPermissionCan } from "@app/components/permissions";
import SecurityClient from "@app/components/utilities/SecurityClient";
import {
BreadcrumbContainer,
Button,
DropdownMenu,
DropdownMenuContent,
@@ -43,7 +42,6 @@ import {
IconButton,
Modal,
ModalContent,
TBreadcrumbFormat,
Tooltip
} from "@app/components/v2";
import { Badge, InstanceIcon, OrgIcon, SubOrgIcon } from "@app/components/v3";
@@ -69,6 +67,7 @@ import { MfaMethod } from "@app/hooks/api/auth/types";
import { getAuthToken } from "@app/hooks/api/reactQuery";
import { Organization, SubscriptionPlan } from "@app/hooks/api/types";
import { AuthMethod } from "@app/hooks/api/users/types";
import { ProjectSelect } from "@app/layouts/ProjectLayout/components/ProjectSelect";
import { navigateUserToOrg } from "@app/pages/auth/LoginPage/Login.utils";
import { ServerAdminsPanel } from "../ServerAdminsPanel/ServerAdminsPanel";
@@ -183,9 +182,6 @@ export const Navbar = () => {
}
}, [subscription, isBillingPage, isModalIntrusive]);
const matches = useRouterState({ select: (s) => s.matches.at(-1)?.context });
const breadcrumbs = matches && "breadcrumbs" in matches ? matches.breadcrumbs : undefined;
const handleOrgChange = async (orgId: string) => {
queryClient.removeQueries({ queryKey: authKeys.getAuthToken });
queryClient.removeQueries({ queryKey: projectKeys.getAllUserProjects() });
@@ -249,7 +245,9 @@ export const Navbar = () => {
const isServerAdminPanel = location.pathname.startsWith("/admin");
const isProjectScope = location.pathname.startsWith(`/organizations/${currentOrg.id}/projects`);
const isProjectScope =
location.pathname.startsWith(`/organizations/${currentOrg.id}/projects`) &&
location.pathname !== `/organizations/${currentOrg.id}/projects`;
const handleOrgNav = async (org: Organization) => {
if (currentOrg?.id === org.id) return;
@@ -279,64 +277,59 @@ export const Navbar = () => {
};
return (
<div className="z-10 flex min-h-12 items-center bg-mineshaft-900 px-4 pt-1">
<div className="mr-auto flex items-center overflow-hidden">
<div className="shrink-0">
<div className="z-10 flex min-h-12 items-center bg-mineshaft-900 px-4">
<div className="mr-auto flex h-full min-w-34 items-center">
<div className="mt-0.5 shrink-0">
<Link to="/organizations/$orgId/projects" params={{ orgId: currentOrg.id }}>
<img alt="infisical logo" src="/images/logotransparent.png" className="h-4" />
</Link>
</div>
<p className="pr-3 pl-1 text-lg text-mineshaft-400/70">/</p>
<ChevronRight size={18} className="mx-3 mt-[3px] text-mineshaft-400/70" />
{isServerAdminPanel ? (
<>
<Link
to="/admin"
className="group flex cursor-pointer items-center gap-2 text-sm text-white transition-all duration-100 hover:text-primary"
>
<InstanceIcon className="size-3.5 text-xs text-bunker-300" />
<div className="whitespace-nowrap">Server Console</div>
</Link>
<p className="pr-3 pl-3 text-lg text-mineshaft-400/70">/</p>
{breadcrumbs ? (
// scott: remove /admin as we show server console above
<BreadcrumbContainer breadcrumbs={breadcrumbs.slice(1) as TBreadcrumbFormat[]} />
) : null}
</>
<Link
to="/admin"
className="group flex cursor-pointer items-center gap-2 text-sm text-white transition-all duration-100 hover:text-primary"
>
<InstanceIcon className="size-3.5 text-xs text-bunker-300" />
<div className="whitespace-nowrap">Server Console</div>
</Link>
) : (
<>
<div className="flex min-w-12 items-center overflow-hidden">
<div
className={twMerge(
"relative flex min-w-16 items-center self-end rounded-t-md border-x border-t pt-1.5 pr-2 pb-2.5 pl-3",
!isProjectScope && !isSubOrganization
? "border-org/15 bg-gradient-to-b from-org/10 to-org/[0.075]"
: "border-transparent"
)}
>
{/* scott: the below is used to hide the top border from the org nav bar */}
{!isProjectScope && !isSubOrganization && (
<div className="absolute -bottom-px left-0 h-px w-full bg-mineshaft-900">
<div className="h-full bg-org/[0.075]" />
</div>
)}
<DropdownMenu modal={false} open={isOrgSelectOpen} onOpenChange={setIsOrgSelectOpen}>
<div className="group flex cursor-pointer items-center gap-2 overflow-hidden text-sm text-white transition-all duration-100 hover:text-primary">
<Badge
asChild
variant="org"
isTruncatable
// TODO(scott): either add badge size/style variant or create designated component for namespace/org nav bar
className={twMerge(
"gap-x-1.5 text-sm",
(isProjectScope || isSubOrganization) &&
"bg-transparent text-mineshaft-200 hover:!bg-transparent hover:underline [&>svg]:!text-org"
)}
<div className="group mr-1 flex min-w-0 cursor-pointer items-center gap-2 overflow-hidden text-sm text-white transition-all duration-100">
<button
className="flex cursor-pointer items-center gap-x-2 truncate whitespace-nowrap"
type="button"
onClick={async () => {
navigate({
to: "/organizations/$orgId/projects",
params: { orgId: currentOrg.id }
});
if (isSubOrganization) {
await router.invalidate({ sync: true }).catch(() => null);
}
}}
>
<button
type="button"
onClick={async () => {
navigate({
to: "/organizations/$orgId/projects",
params: { orgId: currentOrg.id }
});
if (isSubOrganization) {
await router.invalidate({ sync: true }).catch(() => null);
}
}}
>
<OrgIcon className="size-[12px]" />
<span>{currentOrg?.name}</span>
</button>
</Badge>
<div className="mr-1 hidden rounded-sm border border-mineshaft-500 px-1 text-xs text-bunker-300 no-underline! md:inline-block">
{getPlan(subscription)}
</div>
<OrgIcon className={twMerge("size-[14px] shrink-0 text-org")} />
<span className="truncate">{currentOrg?.name}</span>
<Badge variant="org" className="hidden lg:inline-flex">
Organization
</Badge>
</button>
{subscription.cardDeclined && (
<Tooltip
content={`Your payment could not be processed${subscription.cardDeclinedReason ? `: ${subscription.cardDeclinedReason}` : ""}. Please update your payment method to continue enjoying premium features.`}
@@ -543,19 +536,15 @@ export const Navbar = () => {
)}
{isProjectScope && (
<>
<p className="pr-3 pl-1 text-lg text-mineshaft-400/70">/</p>
{breadcrumbs ? (
<BreadcrumbContainer
className="min-w-[15rem] flex-1"
breadcrumbs={[breadcrumbs[0]] as TBreadcrumbFormat[]}
/>
) : null}
<ChevronRight size={18} className="mx-3 mt-[3px] text-mineshaft-400/70" />
<ProjectSelect />
</>
)}
</>
)}
</div>
{subscription && subscription.slug === "starter" && !subscription.has_used_trial && (
{subscription && subscription.slug === "starter" && !subscription.has_used_trial ? (
<Tooltip content="Start Free Pro Trial">
<Button
variant="plain"
@@ -576,6 +565,10 @@ export const Navbar = () => {
Free Pro Trial
</Button>
</Tooltip>
) : (
<div className="mt-0.5 mr-3 hidden rounded-sm border border-mineshaft-400 px-1 text-xs text-mineshaft-100 no-underline! opacity-50 md:inline-block">
{getPlan(subscription)}
</div>
)}
{/* eslint-disable-next-line no-nested-ternary */}
{!location.pathname.startsWith("/admin") ? (
@@ -601,7 +594,7 @@ export const Navbar = () => {
}}
>
<UserPlusIcon className="inline-block size-3.5" />
<span className="ml-2 hidden md:inline-block">Invite Members</span>
<span className="ml-2 hidden md:inline-block">Invite Users</span>
</Link>
) : null
}
@@ -705,7 +698,7 @@ export const Navbar = () => {
}}
>
<DropdownMenuItem icon={<FontAwesomeIcon icon={faUserPlus} />}>
Invite Members
Invite Users
</DropdownMenuItem>
</Link>
) : null
@@ -19,9 +19,9 @@ export const OrgNavBar = ({ isHidden }: Props) => {
const variant = isRootOrganization ? "org" : "namespace";
return (
<>
<div className="bg-mineshaft-900">
{!isHidden && (
<div className="dark flex w-full flex-col overflow-x-hidden border-b border-mineshaft-600 bg-mineshaft-900 px-4">
<div className="dark flex w-full flex-col overflow-x-hidden border-y border-t-org/15 border-b-org/5 bg-gradient-to-b from-org/[0.075] to-org/[0.025] px-4 pt-0.5">
<motion.div
key="menu-org-items"
initial={{ x: -150 }}
@@ -114,6 +114,6 @@ export const OrgNavBar = ({ isHidden }: Props) => {
isOpen={popUp?.createOrg?.isOpen}
onClose={() => handlePopUpToggle("createOrg", false)}
/>
</>
</div>
);
};
+2 -2
View File
@@ -29,8 +29,8 @@ export const PamLayout = () => {
return (
<>
<div className="dark flex h-full w-full flex-col overflow-x-hidden">
<div className="border-b border-mineshaft-600 bg-mineshaft-900">
<div className="dark flex h-full w-full flex-col overflow-x-hidden bg-mineshaft-900">
<div className="border-y border-t-project/10 border-b-project/5 bg-gradient-to-b from-project/[0.075] to-project/[0.025] px-4 pt-0.5">
<motion.div
key="menu-project-items"
initial={{ x: -150 }}
@@ -29,8 +29,8 @@ export const PkiManagerLayout = () => {
const location = useLocation();
return (
<div className="dark flex h-full w-full flex-col overflow-x-hidden">
<div className="border-b border-mineshaft-600 bg-mineshaft-900">
<div className="dark flex h-full w-full flex-col overflow-x-hidden bg-mineshaft-900">
<div className="border-y border-t-project/10 border-b-project/5 bg-gradient-to-b from-project/[0.075] to-project/[0.025] px-4 pt-0.5">
<motion.div
key="menu-project-items"
initial={{ x: -150 }}
@@ -92,7 +92,11 @@ export const ProjectSelect = () => {
}, [projects, projectFavorites, currentWorkspace]);
return (
<div className="mr-2 flex items-center gap-1 overflow-hidden">
<div className="relative mr-2 flex min-w-16 items-center gap-1 self-end rounded-t-md border-x border-t border-project/10 bg-gradient-to-b from-project/10 to-project/[0.075] pt-1.5 pr-1 pb-2.5 pl-3">
{/* scott: the below is used to hide the top border from the org nav bar */}
<div className="absolute -bottom-px left-0 h-px w-full bg-mineshaft-900">
<div className="h-full bg-project/[0.075]" />
</div>
<DropdownMenu modal={false}>
<Link
to={getProjectHomePage(currentWorkspace.type, currentWorkspace.environments)}
@@ -100,16 +104,12 @@ export const ProjectSelect = () => {
projectId: currentWorkspace.id,
orgId: currentWorkspace.orgId
}}
className="group flex cursor-pointer items-center gap-x-1.5 overflow-hidden hover:text-white"
className="group flex cursor-pointer items-center gap-x-2 overflow-hidden pt-0.5 text-sm text-white"
>
<p className="inline-block truncate text-mineshaft-200 group-hover:underline">
{currentWorkspace?.name}
</p>
<Badge variant="project">
<ProjectIcon />
<span className="hidden sm:inline-block">
{currentWorkspace.type ? PROJECT_TYPE_NAME[currentWorkspace.type] : "Project"}
</span>
<ProjectIcon className="size-[14px] shrink-0 text-project" />
<span className="truncate">{currentWorkspace?.name}</span>
<Badge variant="project" className="hidden lg:inline-flex">
{currentWorkspace.type ? PROJECT_TYPE_NAME[currentWorkspace.type] : "Project"}
</Badge>
</Link>
<DropdownMenuTrigger asChild>
@@ -118,7 +118,7 @@ export const ProjectSelect = () => {
variant="plain"
colorSchema="secondary"
ariaLabel="switch-project"
className="px-2 py-1"
className="top-px px-2 py-1"
>
<FontAwesomeIcon icon={faCaretDown} className="text-xs text-bunker-300" />
</IconButton>
@@ -39,8 +39,8 @@ export const SecretManagerLayout = () => {
(secretApprovalReqCount?.open || 0) + (accessApprovalRequestCount?.pendingCount || 0);
return (
<div className="dark flex h-full w-full flex-col overflow-x-hidden">
<div className="border-b border-mineshaft-600 bg-mineshaft-900">
<div className="dark flex h-full w-full flex-col overflow-x-hidden bg-mineshaft-900">
<div className="border-y border-t-project/10 border-b-project/5 bg-gradient-to-b from-project/[0.075] to-project/[0.025] px-4 pt-0.5">
<motion.div
key="menu-project-items"
initial={{ x: -150 }}
@@ -38,8 +38,8 @@ export const SecretScanningLayout = () => {
);
return (
<div className="dark flex h-full w-full flex-col overflow-x-hidden">
<div className="border-b border-mineshaft-600 bg-mineshaft-900">
<div className="dark flex h-full w-full flex-col overflow-x-hidden bg-mineshaft-900">
<div className="border-y border-t-project/10 border-b-project/5 bg-gradient-to-b from-project/[0.075] to-project/[0.025] px-4 pt-0.5">
<motion.div
key="menu-project-items"
initial={{ x: -150 }}
+2 -2
View File
@@ -20,8 +20,8 @@ export const SshLayout = () => {
const location = useLocation();
return (
<div className="dark flex h-full w-full flex-col overflow-x-hidden">
<div className="border-b border-mineshaft-600 bg-mineshaft-900">
<div className="dark flex h-full w-full flex-col overflow-x-hidden bg-mineshaft-900">
<div className="border-y border-t-project/10 border-b-project/5 bg-gradient-to-b from-project/[0.075] to-project/[0.025] px-4 pt-0.5">
<motion.div
key="menu-project-items"
initial={{ x: -150 }}
+2
View File
@@ -12,6 +12,8 @@ import { NotFoundPage } from "./pages/public/NotFoundPage/NotFoundPage";
// Import the generated route tree
import { routeTree } from "./routeTree.gen";
import "@fontsource/inter/400.css";
import "@fontsource/inter/500.css";
import "@xyflow/react/dist/style.css";
import "nprogress/nprogress.css";
import "react-toastify/dist/ReactToastify.css";
@@ -17,7 +17,7 @@ export const AccessManagementPage = () => {
<div className="mx-auto mb-6 w-full max-w-8xl">
<PageHeader
scope="instance"
title="Access Control"
title="Server Admin Access Control"
description="Manage server admins within your Infisical instance."
/>
<ServerAdminsTable />
@@ -29,7 +29,7 @@ export const ResourceOverviewPage = () => {
Users
</Tab>
<Tab variant="instance" value="tab-identities">
Identities
Machine Identities
</Tab>
</TabList>
<TabPanel value="tab-organizations">
@@ -90,7 +90,7 @@ const IdentityPanelTable = ({
value={searchIdentityFilter}
onChange={(e) => setSearchIdentityFilter(e.target.value)}
leftIcon={<FontAwesomeIcon icon={faMagnifyingGlass} />}
placeholder="Search identities by name..."
placeholder="Search machine identities by name..."
className="flex-1"
/>
</div>
@@ -350,8 +350,8 @@ const ViewMembersModalContent = ({
className="my-auto bg-mineshaft-700"
title={
members.length
? "No organization members match search..."
: "No organization members found"
? "No organization users match search..."
: "No organization users found"
}
icon={faUsers}
/>
@@ -27,6 +27,10 @@ import {
TCloudflareZone,
useCloudflareConnectionListZones
} from "@app/hooks/api/appConnections/cloudflare";
import {
TDNSMadeEasyZone,
useDNSMadeEasyConnectionListZones
} from "@app/hooks/api/appConnections/dns-made-easy";
import { AppConnection } from "@app/hooks/api/appConnections/enums";
import {
AcmeDnsProvider,
@@ -211,6 +215,11 @@ export const ExternalCaModal = ({ popUp, handlePopUpToggle }: Props) => {
enabled: caType === CaType.ACME
});
const { data: availableDNSMadeEasyConnections, isPending: isDNSMadeEasyPending } =
useListAvailableAppConnections(AppConnection.DNSMadeEasy, currentProject.id, {
enabled: caType === CaType.ACME
});
const { data: availableAzureConnections, isPending: isAzurePending } =
useListAvailableAppConnections(AppConnection.AzureADCS, currentProject.id, {
enabled: caType === CaType.AZURE_AD_CS
@@ -220,16 +229,24 @@ export const ExternalCaModal = ({ popUp, handlePopUpToggle }: Props) => {
if (caType === CaType.AZURE_AD_CS) {
return availableAzureConnections || [];
}
return [...(availableRoute53Connections || []), ...(availableCloudflareConnections || [])];
return [
...(availableRoute53Connections || []),
...(availableCloudflareConnections || []),
...(availableDNSMadeEasyConnections || [])
];
}, [
caType,
availableRoute53Connections,
availableCloudflareConnections,
availableDNSMadeEasyConnections,
availableAzureConnections
]);
const isPending =
isRoute53Pending || isCloudflarePending || (isAzurePending && caType === CaType.AZURE_AD_CS);
isRoute53Pending ||
isCloudflarePending ||
isDNSMadeEasyPending ||
(isAzurePending && caType === CaType.AZURE_AD_CS);
const dnsAppConnection =
caType === CaType.ACME && configuration && "dnsAppConnection" in configuration
@@ -241,6 +258,11 @@ export const ExternalCaModal = ({ popUp, handlePopUpToggle }: Props) => {
enabled: dnsProvider === AcmeDnsProvider.Cloudflare && !!dnsAppConnection.id
});
const { data: dnsMadeEasyZones = [], isPending: isDNSMadeEasyZonesPending } =
useDNSMadeEasyConnectionListZones(dnsAppConnection.id, {
enabled: dnsProvider === AcmeDnsProvider.DNSMadeEasy && !!dnsAppConnection.id
});
// Populate form with CA data when editing
useEffect(() => {
if (ca && !isCaLoading) {
@@ -500,6 +522,32 @@ export const ExternalCaModal = ({ popUp, handlePopUpToggle }: Props) => {
)}
/>
)}
{dnsProvider === AcmeDnsProvider.DNSMadeEasy && (
<Controller
name="configuration.dnsProviderConfig.hostedZoneId"
control={control}
render={({ field: { value, onChange }, fieldState: { error } }) => (
<FormControl
errorText={error?.message}
isError={Boolean(error?.message)}
label="Zone"
>
<FilterableSelect
isLoading={isDNSMadeEasyZonesPending && !!dnsAppConnection.id}
isDisabled={!dnsAppConnection.id}
value={dnsMadeEasyZones.find((zone) => zone.id === value)}
onChange={(option) => {
onChange((option as SingleValue<TDNSMadeEasyZone>)?.id ?? null);
}}
options={dnsMadeEasyZones}
placeholder="Select a zone..."
getOptionLabel={(option) => option.name}
getOptionValue={(option) => option.id}
/>
</FormControl>
)}
/>
)}
<Controller
control={control}
defaultValue=""
@@ -49,7 +49,7 @@ export const IntegrationsListPage = () => {
<div className="mb-8">
<PageHeader
scope={ProjectType.CertificateManager}
title="Integrations"
title="Project Integrations"
description="Manage integrations with third-party certificate services."
/>
<Tabs orientation="vertical" value={currentTab} onValueChange={updateSelectedTab}>
@@ -577,7 +577,7 @@ export const CreateProfileModal = ({
isDisabled={Boolean(isEdit)}
>
<SelectItem value="ca">Certificate Authority</SelectItem>
<SelectItem value="self-signed">Self-signed</SelectItem>
<SelectItem value="self-signed">Self-Signed</SelectItem>
</Select>
</FormControl>
)}
@@ -1,7 +1,10 @@
import { Helmet } from "react-helmet";
import { useTranslation } from "react-i18next";
import { Link } from "@tanstack/react-router";
import { InfoIcon } from "lucide-react";
import { PageHeader, Tab, TabList, TabPanel, Tabs } from "@app/components/v2";
import { useOrganization } from "@app/context";
import { ProjectType } from "@app/hooks/api/projects/types";
import { ProjectGeneralTab } from "@app/pages/project/SettingsPage/components/ProjectGeneralTab";
@@ -15,6 +18,7 @@ const tabs = [
export const SettingsPage = () => {
const { t } = useTranslation();
const { currentOrg } = useOrganization();
return (
<div className="flex h-full w-full justify-center bg-bunker-800 text-white">
@@ -22,7 +26,17 @@ export const SettingsPage = () => {
<title>{t("common.head-title", { title: t("settings.project.title") })}</title>
</Helmet>
<div className="w-full max-w-8xl">
<PageHeader scope={ProjectType.CertificateManager} title={t("settings.project.title")} />
<PageHeader scope={ProjectType.CertificateManager} title={t("settings.project.title")}>
<Link
to="/organizations/$orgId/settings"
params={{
orgId: currentOrg.id
}}
className="flex items-center gap-x-1.5 text-xs whitespace-nowrap text-neutral hover:underline"
>
<InfoIcon size={12} /> Looking for organization settings?
</Link>
</PageHeader>
<Tabs orientation="vertical" defaultValue={tabs[0].key}>
<TabList>
{tabs.map((tab) => (
@@ -20,7 +20,7 @@ export const OverviewPage = () => {
<div className="mx-auto mb-6 w-full max-w-8xl">
<PageHeader
scope={ProjectType.KMS}
title="Overview"
title="Project Overview"
description="Manage keys and perform cryptographic operations."
/>
<ProjectPermissionCan
@@ -1,7 +1,10 @@
import { Helmet } from "react-helmet";
import { useTranslation } from "react-i18next";
import { Link } from "@tanstack/react-router";
import { InfoIcon } from "lucide-react";
import { PageHeader, Tab, TabList, TabPanel, Tabs } from "@app/components/v2";
import { useOrganization } from "@app/context";
import { ProjectType } from "@app/hooks/api/projects/types";
import { ProjectGeneralTab } from "@app/pages/project/SettingsPage/components/ProjectGeneralTab";
@@ -16,6 +19,8 @@ const tabs = [
export const SettingsPage = () => {
const { t } = useTranslation();
const { currentOrg } = useOrganization();
return (
<div className="flex h-full w-full justify-center bg-bunker-800 text-white">
<Helmet>
@@ -24,9 +29,19 @@ export const SettingsPage = () => {
<div className="w-full max-w-8xl">
<PageHeader
scope={ProjectType.KMS}
title="Settings"
title="Project Settings"
description="Configure general project settings"
/>
>
<Link
to="/organizations/$orgId/settings"
params={{
orgId: currentOrg.id
}}
className="flex items-center gap-x-1.5 text-xs whitespace-nowrap text-neutral hover:underline"
>
<InfoIcon size={12} /> Looking for organization settings?
</Link>
</PageHeader>
<Tabs orientation="vertical" defaultValue={tabs[0].key}>
<TabList>
{tabs.map((tab) => (
@@ -58,7 +58,7 @@ export const AccessManagementPage = () => {
},
{
key: OrgAccessControlTabSections.Identities,
label: "Identities",
label: "Machine Identities",
isHidden: permission.cannot(
OrgPermissionIdentityActions.Read,
OrgPermissionSubjects.Identity
@@ -83,8 +83,8 @@ export const AccessManagementPage = () => {
<div className="mx-auto mb-6 w-full max-w-8xl">
<PageHeader
scope={isSubOrganization ? "namespace" : "org"}
title="Access Control"
description="Manage fine-grained access for users, groups, roles, and identities within your organization resources."
title={`${isSubOrganization ? "Sub-Organization" : "Organization"} Access Control`}
description="Manage fine-grained access for users, groups, roles, and machine identities within your organization resources."
/>
{!currentOrg.shouldUseNewPrivilegeSystem && (
<div className="mt-4 mb-4 flex flex-col rounded-r border-l-2 border-l-primary bg-mineshaft-300/5 px-4 py-2.5">
@@ -51,19 +51,19 @@ export const OrgGroupsSection = () => {
<div className="mb-6 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
<div className="mb-4 flex flex-wrap items-center justify-between gap-2">
<div className="flex items-center gap-x-2">
<p className="text-xl font-medium text-mineshaft-100">Groups</p>
<p className="text-xl font-medium text-mineshaft-100">Organization Groups</p>
<DocumentationLinkBadge href="https://infisical.com/docs/documentation/platform/groups" />
</div>
<OrgPermissionCan I={OrgPermissionGroupActions.Create} a={OrgPermissionSubjects.Groups}>
{(isAllowed) => (
<Button
colorSchema="secondary"
variant="outline_bg"
type="submit"
leftIcon={<FontAwesomeIcon icon={faPlus} />}
onClick={() => handleAddGroupModal()}
isDisabled={!isAllowed}
>
Create Group
Create Organization Group
</Button>
)}
</OrgPermissionCan>
@@ -159,7 +159,7 @@ export const OrgGroupsTable = ({ handlePopUpOpen }: Props) => {
value={search}
onChange={(e) => setSearch(e.target.value)}
leftIcon={<FontAwesomeIcon icon={faMagnifyingGlass} />}
placeholder="Search groups..."
placeholder="Search organization groups..."
/>
<TableContainer className="mt-4">
<Table>
@@ -205,7 +205,7 @@ export const OrgGroupsTable = ({ handlePopUpOpen }: Props) => {
</Th>
<Th>
<div className="flex items-center">
Role
Organization Role
<IconButton
variant="plain"
className={`ml-2 ${orderBy === GroupsOrderBy.Role ? "" : "opacity-30"}`}
@@ -154,7 +154,9 @@ export const IdentityAuthTemplateModal = ({ popUp, handlePopUpToggle }: Props) =
onOpenChange={handleClose}
>
<ModalContent
title={isEdit ? "Edit Identity Auth Template" : "Create Identity Auth Template"}
title={
isEdit ? "Edit Machine Identity Auth Template" : "Create Machine Identity Auth Template"
}
subTitle={
isEdit ? "Update the authentication template" : "Create a new authentication template"
}
@@ -73,7 +73,7 @@ export const IdentitySection = withPermission(
});
createNotification({
text: "Successfully deleted identity",
text: "Successfully deleted machine identity",
type: "success"
});
@@ -99,7 +99,9 @@ export const IdentitySection = withPermission(
<div className="rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
<div className="mb-4 flex flex-wrap items-center justify-between gap-2">
<div className="flex flex-1 items-center gap-x-2">
<p className="text-xl font-medium text-mineshaft-100">Identities</p>
<p className="text-xl font-medium text-mineshaft-100">
Organization Machine Identities
</p>
<DocumentationLinkBadge href="https://infisical.com/docs/documentation/platform/identities/machine-identities" />
</div>
<div className="flex items-center">
@@ -116,7 +118,7 @@ export const IdentitySection = withPermission(
if (!isMoreIdentitiesAllowed && !isEnterprise) {
handlePopUpOpen("upgradePlan", {
description:
"You can add more identities if you upgrade your Infisical Pro plan."
"You can add more machine identities if you upgrade your Infisical Pro plan."
});
return;
}
@@ -129,7 +131,9 @@ export const IdentitySection = withPermission(
}}
isDisabled={!isAllowed}
>
Create Identity
{isSubOrganization
? "Add Machine Identity to Sub-Organization"
: "Create Organization Machine Identity"}
</Button>
)}
</OrgPermissionCan>
@@ -141,7 +145,9 @@ export const IdentitySection = withPermission(
<div className="mt-4 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
<div className="mb-4 flex flex-wrap items-center justify-between gap-2">
<div className="flex items-center gap-x-2">
<p className="text-xl font-medium text-mineshaft-100">Identity Auth Templates</p>
<p className="text-xl font-medium text-mineshaft-100">
Machine Identity Auth Templates
</p>
<DocumentationLinkBadge href="https://infisical.com/docs/documentation/platform/identities/auth-templates" />
</div>
<OrgPermissionCan
@@ -150,14 +156,14 @@ export const IdentitySection = withPermission(
>
{(isAllowed) => (
<Button
colorSchema="secondary"
variant="outline_bg"
type="submit"
leftIcon={<FontAwesomeIcon icon={faPlus} />}
onClick={() => {
if (subscription && !subscription.machineIdentityAuthTemplates) {
handlePopUpOpen("upgradePlan", {
isEnterpriseFeature: true,
text: "Your current plan does not include access to creating Identity Auth Templates. To unlock this feature, please upgrade to Infisical Enterprise plan."
text: "Your current plan does not include access to creating Machine Identity Auth Templates. To unlock this feature, please upgrade to Infisical Enterprise plan."
});
return;
}
@@ -197,9 +203,11 @@ export const IdentitySection = withPermission(
>
<ModalContent
bodyClassName="overflow-visible"
title="Add Identity"
title="Add Machine Identity"
subTitle={
isSubOrganization ? "Create a new identity or assign an existing identity" : undefined
isSubOrganization
? "Create a new machine identity or assign an existing one"
: undefined
}
>
<AnimatePresence mode="wait">
@@ -224,11 +232,11 @@ export const IdentitySection = withPermission(
>
<div className="flex items-center gap-2">
<PlusIcon size="1rem" />
<div>Create New Identity</div>
<div>Create Machine Identity</div>
</div>
<div className="mt-2 text-xs text-mineshaft-300">
Create a new machine identity specifically for this sub-organization. This
identity will be managed at the sub-organization level.
machine identity will be managed at the sub-organization level.
</div>
</div>
<div
@@ -244,11 +252,11 @@ export const IdentitySection = withPermission(
>
<div className="flex items-center gap-2">
<LinkIcon size="1rem" />
<div>Assign Existing Identity</div>
<div>Assign Existing Machine Identity</div>
</div>
<div className="mt-2 text-xs text-mineshaft-300">
Assign an existing identity from your parent organization. The identity will
continue to be managed at its original scope.
Assign an existing machine identity from your parent organization. The machine
identity will continue to be managed at its original scope.
</div>
</div>
</motion.div>
@@ -152,7 +152,7 @@ export const IdentityTable = ({ handlePopUpOpen }: Props) => {
});
createNotification({
text: "Successfully updated identity role",
text: "Successfully updated machine identity role",
type: "success"
});
};
@@ -178,7 +178,7 @@ export const IdentityTable = ({ handlePopUpOpen }: Props) => {
<DropdownMenu>
<DropdownMenuTrigger asChild>
<IconButton
ariaLabel="Filter Identities"
ariaLabel="Filter Machine Identities"
variant="plain"
size="sm"
className={twMerge(
@@ -200,7 +200,7 @@ export const IdentityTable = ({ handlePopUpOpen }: Props) => {
</DropdownSubMenuTrigger>
<DropdownSubMenuContent className="max-h-80 thin-scrollbar overflow-y-auto rounded-l-none">
<DropdownMenuLabel className="sticky top-0 bg-mineshaft-900">
Apply Roles to Filter Identities
Filter Organization Machine Identities by Role
</DropdownMenuLabel>
{roles?.map(({ id, slug, name }) => (
<DropdownMenuItem
@@ -229,7 +229,7 @@ export const IdentityTable = ({ handlePopUpOpen }: Props) => {
value={search}
onChange={(e) => setSearch(e.target.value)}
leftIcon={<FontAwesomeIcon icon={faMagnifyingGlass} />}
placeholder="Search identities by name..."
placeholder="Search machine identities by name..."
/>
</div>
<TableContainer>
@@ -258,7 +258,7 @@ export const IdentityTable = ({ handlePopUpOpen }: Props) => {
</Th>
<Th>
<div className="flex items-center">
Role
Organization Role
<IconButton
variant="plain"
className={`ml-2 ${orderBy === OrgIdentityOrderBy.Role ? "" : "opacity-30"}`}
@@ -407,7 +407,7 @@ export const IdentityTable = ({ handlePopUpOpen }: Props) => {
}}
isDisabled={!isAllowed}
>
Edit Identity {isSubOrgIdentity ? "" : "Membership"}
Edit Machine Identity {isSubOrgIdentity ? "" : "Membership"}
</DropdownMenuItem>
)}
</OrgPermissionCan>
@@ -428,7 +428,7 @@ export const IdentityTable = ({ handlePopUpOpen }: Props) => {
icon={<FontAwesomeIcon icon={faTrash} />}
>
{isSubOrgIdentity
? "Delete Identity"
? "Delete Machine Identity"
: "Remove From Sub-Organization"}
</DropdownMenuItem>
)}
@@ -455,8 +455,8 @@ export const IdentityTable = ({ handlePopUpOpen }: Props) => {
<EmptyState
title={
debouncedSearch.trim().length > 0 || filter.roles?.length > 0
? "No identities match search filter"
: "No identities have been created in this organization"
? "No machine identities match search filter"
: "No machine identities have been created in this organization"
}
icon={faServer}
/>
@@ -78,11 +78,11 @@ export const OrgIdentityLinkForm = ({ onClose }: Props) => {
control={control}
name="identity"
render={({ field: { onChange, value }, fieldState: { error } }) => (
<FormControl label="Identity" errorText={error?.message} isError={Boolean(error)}>
<FormControl label="Machine Identity" errorText={error?.message} isError={Boolean(error)}>
<FilterableSelect
value={value}
onChange={onChange}
placeholder="Select identity..."
placeholder="Select machine identity..."
// onInputChange={setSearchValue}
options={rootOrgIdentities}
getOptionValue={(option) => option.id}
@@ -164,7 +164,7 @@ export const OrgIdentityModal = ({ popUp, handlePopUpToggle }: Props) => {
}
createNotification({
text: `Successfully ${popUp?.identity?.data ? "updated" : "created"} identity`,
text: `Successfully ${popUp?.identity?.data ? "updated" : "created"} machine identity`,
type: "success"
});
@@ -255,9 +255,7 @@ export const OrgIdentityModal = ({ popUp, handlePopUpToggle }: Props) => {
/>
</div>
<div className="grow">
{i === 0 && (
<FormLabel label="Value" className="text-xs text-mineshaft-400" isOptional />
)}
{i === 0 && <FormLabel label="Value" className="text-xs text-mineshaft-400" />}
<Controller
control={control}
name={`metadata.${i}.value`}
@@ -159,14 +159,16 @@ export const AddOrgMemberModal = ({
)
);
setCompleteInviteLinks(data?.completeInviteLinks ?? null);
if (data?.completeInviteLinks && data?.completeInviteLinks.length > 0) {
setCompleteInviteLinks(data.completeInviteLinks);
}
// only show this notification when email is configured.
// A [completeInviteLink] will not be sent if smtp is configured
if (!data.completeInviteLinks) {
if (!data.completeInviteLinks?.length) {
createNotification({
text: "Successfully invited user to the organization.",
text: `Successfully invited user${usernames.length > 1 ? "s" : ""} to the organization.`,
type: "success"
});
}
@@ -205,13 +205,13 @@ export const OrgMembersSection = () => {
<div className="mb-6 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
<div className="mb-4 flex flex-wrap items-center justify-between gap-2">
<div className="flex items-center gap-x-2">
<p className="text-xl font-medium text-mineshaft-100">Users</p>
<p className="text-xl font-medium text-mineshaft-100">Organization Users</p>
<DocumentationLinkBadge href="https://infisical.com/docs/documentation/platform/identities/user-identities" />
</div>
<OrgPermissionCan I={OrgPermissionActions.Create} a={OrgPermissionSubjects.Member}>
{(isAllowed) => (
<Button
colorSchema="secondary"
variant="outline_bg"
type="submit"
leftIcon={<UserPlusIcon size={16} />}
onClick={() =>
@@ -219,7 +219,9 @@ export const OrgMembersSection = () => {
}
isDisabled={!isAllowed}
>
{isSubOrganization ? "Add Members" : "Invite Members"}
{isSubOrganization
? "Add Users to Sub-Organization"
: "Invite Users to Organization"}
</Button>
)}
</OrgPermissionCan>
@@ -336,7 +336,7 @@ export const OrgMembersTable = ({
</DropdownSubMenuTrigger>
<DropdownSubMenuContent className="max-h-80 thin-scrollbar overflow-y-auto rounded-l-none">
<DropdownMenuLabel className="sticky top-0 bg-mineshaft-900">
Apply Roles to Filter Users
Filter Organization Users by Role
</DropdownMenuLabel>
{roles?.map(({ id, slug, name }) => (
<DropdownMenuItem
@@ -365,7 +365,7 @@ export const OrgMembersTable = ({
value={search}
onChange={(e) => setSearch(e.target.value)}
leftIcon={<FontAwesomeIcon icon={faMagnifyingGlass} />}
placeholder="Search members..."
placeholder="Search organization users..."
/>
</div>
<TableContainer className="mt-4">
@@ -434,7 +434,7 @@ export const OrgMembersTable = ({
</Th>
<Th className="w-1/3">
<div className="flex items-center">
Role
Organization Role
<IconButton
variant="plain"
className={`ml-2 ${orderBy === OrgMembersOrderBy.Role ? "" : "opacity-30"}`}
@@ -727,8 +727,8 @@ export const OrgMembersTable = ({
<EmptyState
title={
members.length
? "No organization members match search..."
: "No organization members found"
? "No organization users match search..."
: "No organization users found"
}
icon={members.length ? faSearch : faUsers}
/>
@@ -199,7 +199,7 @@ export const OrgRoleTable = () => {
<OrgPermissionCan I={OrgPermissionActions.Create} a={OrgPermissionSubjects.Role}>
{(isAllowed) => (
<Button
colorSchema="secondary"
variant="outline_bg"
type="submit"
leftIcon={<FontAwesomeIcon icon={faPlus} />}
onClick={() => {
@@ -207,7 +207,7 @@ export const OrgRoleTable = () => {
}}
isDisabled={!isAllowed}
>
Add Role
Add Organization Role
</Button>
)}
</OrgPermissionCan>
@@ -216,7 +216,7 @@ export const OrgRoleTable = () => {
value={search}
onChange={(e) => setSearch(e.target.value)}
leftIcon={<FontAwesomeIcon icon={faMagnifyingGlass} />}
placeholder="Search roles..."
placeholder="Search organization roles..."
className="flex-1"
containerClassName="mb-4"
/>
@@ -441,7 +441,7 @@ export const OrgRoleTable = () => {
<EmptyState
title={
roles?.length
? "No roles match search..."
? "No organization roles match search..."
: "This organization does not have any roles"
}
icon={roles?.length ? faSearch : undefined}
@@ -27,7 +27,7 @@ export const AppConnectionsPage = withPermission(
<PageHeader
scope={isSubOrganization ? "namespace" : "org"}
className="w-full"
title="App Connections"
title={`${isSubOrganization ? "Sub-Organization" : "Organization"} App Connections`}
description="Manage organization App Connections"
/>
<div className="mb-4 flex w-full flex-col rounded-md border border-blue-500/50 bg-blue-500/30 px-4 py-2 text-sm text-blue-200">
@@ -24,6 +24,7 @@ import { ChefConnectionForm } from "./ChefConnectionForm";
import { CloudflareConnectionForm } from "./CloudflareConnectionForm";
import { DatabricksConnectionForm } from "./DatabricksConnectionForm";
import { DigitalOceanConnectionForm } from "./DigitalOceanConnectionForm";
import { DNSMadeEasyConnectionForm } from "./DNSMadeEasyConnectionForm";
import { FlyioConnectionForm } from "./FlyioConnectionForm";
import { GcpConnectionForm } from "./GcpConnectionForm";
import { GitHubConnectionForm } from "./GitHubConnectionForm";
@@ -148,6 +149,8 @@ const CreateForm = ({ app, onComplete, projectId }: CreateFormProps) => {
return <GitLabConnectionForm onSubmit={onSubmit} projectId={projectId} />;
case AppConnection.Cloudflare:
return <CloudflareConnectionForm onSubmit={onSubmit} />;
case AppConnection.DNSMadeEasy:
return <DNSMadeEasyConnectionForm onSubmit={onSubmit} />;
case AppConnection.Bitbucket:
return <BitbucketConnectionForm onSubmit={onSubmit} />;
case AppConnection.Zabbix:
@@ -306,6 +309,8 @@ const UpdateForm = ({ appConnection, onComplete }: UpdateFormProps) => {
);
case AppConnection.Cloudflare:
return <CloudflareConnectionForm onSubmit={onSubmit} appConnection={appConnection} />;
case AppConnection.DNSMadeEasy:
return <DNSMadeEasyConnectionForm onSubmit={onSubmit} appConnection={appConnection} />;
case AppConnection.Bitbucket:
return <BitbucketConnectionForm onSubmit={onSubmit} appConnection={appConnection} />;
case AppConnection.Zabbix:
@@ -0,0 +1,157 @@
import { Controller, FormProvider, useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { z } from "zod";
import {
Button,
FormControl,
Input,
ModalClose,
SecretInput,
Select,
SelectItem
} from "@app/components/v2";
import { APP_CONNECTION_MAP, getAppConnectionMethodDetails } from "@app/helpers/appConnections";
import { TDNSMadeEasyConnection } from "@app/hooks/api/appConnections";
import { AppConnection } from "@app/hooks/api/appConnections/enums";
import { DNSMadeEasyConnectionMethod } from "@app/hooks/api/appConnections/types/dns-made-easy-connection";
import {
genericAppConnectionFieldsSchema,
GenericAppConnectionsFields
} from "./GenericAppConnectionFields";
type Props = {
appConnection?: TDNSMadeEasyConnection;
onSubmit: (formData: FormData) => Promise<void>;
};
const rootSchema = genericAppConnectionFieldsSchema.extend({
app: z.literal(AppConnection.DNSMadeEasy)
});
const formSchema = z.discriminatedUnion("method", [
rootSchema.extend({
method: z.literal(DNSMadeEasyConnectionMethod.APIKeySecret),
credentials: z.object({
apiKey: z.string().trim().min(1, "API Key required"),
secretKey: z.string().trim().min(1, "Secret Key required")
})
})
]);
type FormData = z.infer<typeof formSchema>;
export const DNSMadeEasyConnectionForm = ({ appConnection, onSubmit }: Props) => {
const isUpdate = Boolean(appConnection);
const form = useForm<FormData>({
resolver: zodResolver(formSchema),
defaultValues: appConnection ?? {
app: AppConnection.DNSMadeEasy,
method: DNSMadeEasyConnectionMethod.APIKeySecret,
credentials: {
apiKey: "",
secretKey: ""
}
}
});
const {
handleSubmit,
control,
formState: { isSubmitting, isDirty }
} = form;
return (
<FormProvider {...form}>
<form onSubmit={handleSubmit(onSubmit)}>
{!isUpdate && <GenericAppConnectionsFields />}
<Controller
name="method"
control={control}
render={({ field: { value, onChange }, fieldState: { error } }) => (
<FormControl
tooltipText={`The method you would like to use to connect with ${
APP_CONNECTION_MAP[AppConnection.DNSMadeEasy].name
}. This field cannot be changed after creation.`}
errorText={error?.message}
isError={Boolean(error?.message)}
label="Method"
>
<Select
isDisabled={isUpdate}
value={value}
onValueChange={(val) => onChange(val)}
className="w-full border border-mineshaft-500"
position="popper"
dropdownContainerClassName="max-w-none"
>
{Object.values(DNSMadeEasyConnectionMethod).map((method) => {
return (
<SelectItem value={method} key={method}>
{getAppConnectionMethodDetails(method).name}{" "}
</SelectItem>
);
})}
</Select>
</FormControl>
)}
/>
<Controller
name="credentials.apiKey"
control={control}
shouldUnregister
render={({ field: { value, onChange }, fieldState: { error } }) => (
<FormControl
errorText={error?.message}
isError={Boolean(error?.message)}
label="API Key"
>
<Input
value={value}
onChange={(e) => onChange(e.target.value)}
placeholder="af1b628f-3272-46aa-9cde-837d0c59155d"
/>
</FormControl>
)}
/>
<Controller
name="credentials.secretKey"
control={control}
shouldUnregister
render={({ field: { value, onChange }, fieldState: { error } }) => (
<FormControl
errorText={error?.message}
isError={Boolean(error?.message)}
label="Secret Key"
>
<SecretInput
containerClassName="text-gray-400 group-focus-within:border-primary-400/50! border border-mineshaft-500 bg-mineshaft-900 px-2.5 py-1.5"
value={value}
onChange={(e) => onChange(e.target.value)}
/>
</FormControl>
)}
/>
<div className="mt-8 flex items-center">
<Button
className="mr-4"
size="sm"
type="submit"
colorSchema="secondary"
isLoading={isSubmitting}
isDisabled={isSubmitting || !isDirty}
>
{isUpdate ? "Update Credentials" : "Connect to DNS Made Easy"}
</Button>
<ModalClose asChild>
<Button colorSchema="secondary" variant="plain">
Cancel
</Button>
</ModalClose>
</div>
</form>
</FormProvider>
);
};
@@ -19,7 +19,7 @@ export const AuditLogsPage = () => {
<div className="w-full max-w-8xl">
<PageHeader
scope={isSubOrganization ? "namespace" : "org"}
title="Audit Logs"
title={`${isSubOrganization ? "Sub-Organization" : "Organization"} Audit Logs`}
description="Audit logs for security and compliance teams to monitor information access."
/>
<LogsSection pageView />
@@ -88,7 +88,7 @@ const Page = () => {
className="mb-4 flex items-center gap-x-2 text-sm text-mineshaft-400"
>
<FontAwesomeIcon icon={faChevronLeft} />
Groups
Organization Groups
</Link>
<PageHeader
scope={isSubOrganization ? "namespace" : "org"}
@@ -55,7 +55,7 @@ const Page = () => {
});
createNotification({
text: "Successfully deleted identity",
text: "Successfully deleted machine identity",
type: "success"
});
@@ -82,11 +82,11 @@ const Page = () => {
className="mb-4 flex items-center gap-x-2 text-sm text-mineshaft-400"
>
<FontAwesomeIcon icon={faChevronLeft} />
Identities
Organization Machine Identities
</Link>
<PageHeader
scope={isSubOrganization ? "namespace" : "org"}
description={`${isSubOrganization ? "Sub-" : ""}Organization Identity`}
description={`${isSubOrganization ? "Sub-" : ""}Organization Machine Identity`}
title={data.identity.name}
>
<div className="flex items-center gap-2">
@@ -111,7 +111,7 @@ const Page = () => {
})
}
>
Unlink Identity
Unlink Machine Identity
</Button>
)}
</OrgPermissionCan>
@@ -142,7 +142,7 @@ const Page = () => {
>
<ModalContent
bodyClassName="overflow-visible"
title={`${popUp?.identity?.data ? "Update" : "Create"} Identity`}
title={`${popUp?.identity?.data ? "Update" : "Create"} Machine Identity`}
>
<OrgIdentityModal popUp={popUp} handlePopUpToggle={handlePopUpToggle} />
</ModalContent>
@@ -45,7 +45,7 @@ export const IdentityDetailsSection = ({ identityId, handlePopUpOpen, isOrgIdent
return data ? (
<div className="rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
<div className="flex items-center justify-between border-b border-mineshaft-400 pb-4">
<h3 className="text-lg font-medium text-mineshaft-100">Identity Details</h3>
<h3 className="text-lg font-medium text-mineshaft-100">Details</h3>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
@@ -86,7 +86,7 @@ export const IdentityDetailsSection = ({ identityId, handlePopUpOpen, isOrgIdent
}}
disabled={!isAllowed}
>
{isOrgIdentity ? "Edit Identity" : "Edit Identity Role"}
{isOrgIdentity ? "Edit Machine Identity" : "Edit Machine Identity Role"}
</DropdownMenuItem>
)}
</OrgPermissionCan>
@@ -110,7 +110,7 @@ export const IdentityDetailsSection = ({ identityId, handlePopUpOpen, isOrgIdent
icon={<FontAwesomeIcon icon={faTrash} />}
disabled={!isAllowed}
>
{!isOrgIdentity ? "Remove From Sub-Organization" : "Delete Identity"}
{!isOrgIdentity ? "Remove From Sub-Organization" : "Delete Machine Identity"}
</DropdownMenuItem>
)}
</OrgPermissionCan>
@@ -119,7 +119,7 @@ export const IdentityDetailsSection = ({ identityId, handlePopUpOpen, isOrgIdent
</div>
<div className="pt-4">
<div className="mb-4">
<p className="text-sm font-medium text-mineshaft-300">Identity ID</p>
<p className="text-sm font-medium text-mineshaft-300">Machine Identity ID</p>
<div className="group flex align-top">
<p className="text-sm text-mineshaft-300">{data.identity.id}</p>
<div className="opacity-0 transition-opacity duration-300 group-hover:opacity-100">
@@ -167,7 +167,7 @@ export const IdentityAddToProjectModal = ({ identityId, popUp, handlePopUpToggle
handlePopUpToggle("addIdentityToProject", isOpen);
}}
>
<ModalContent bodyClassName="overflow-visible" title="Add Identity to Project">
<ModalContent bodyClassName="overflow-visible" title="Add Machine Identity to Project">
<Content identityId={identityId} handlePopUpToggle={handlePopUpToggle} />
</ModalContent>
</Modal>
@@ -151,7 +151,7 @@ export const IdentityProjectsTable = ({ identityId, handlePopUpOpen }: Props) =>
title={
projectMemberships.length
? "No projects match search..."
: "This identity has not been assigned to any projects"
: "This machine identity has not been assigned to any projects"
}
icon={projectMemberships.length ? faSearch : faFolder}
/>
@@ -55,10 +55,10 @@ const formSchemaWithIdentity = baseFormSchema.extend({
id: z.string(),
name: z.string()
},
{ required_error: "Identity is required" }
{ required_error: "Machine identity is required" }
)
.nullable()
.refine((val) => val !== null, { message: "Identity is required" })
.refine((val) => val !== null, { message: "Machine identity is required" })
});
const formSchemaWithToken = baseFormSchema.extend({
@@ -275,8 +275,8 @@ export const GatewayCliDeploymentMethod = () => {
{canCreateToken && autogenerateToken ? (
<>
<FormLabel
label="Identity"
tooltipText="The identity that your gateway will use for authentication."
label="Machine Identity"
tooltipText="The machine identity that your gateway will use for authentication."
className="mt-4"
/>
<FilterableSelect
@@ -290,7 +290,7 @@ export const GatewayCliDeploymentMethod = () => {
)
}
isLoading={isIdentitiesLoading}
placeholder="Select identity..."
placeholder="Select machine identity..."
options={identityMembershipOrgs.map((membership) => membership.identity)}
getOptionValue={(option) => option.id}
getOptionLabel={(option) => option.name}
@@ -300,14 +300,14 @@ export const GatewayCliDeploymentMethod = () => {
) : (
<>
<FormLabel
label="Identity Token"
tooltipText="The identity token that your gateway will use for authentication."
label="Machine Identity Token"
tooltipText="The machine identity token that your gateway will use for authentication."
className="mt-4"
/>
<Input
value={identityToken}
onChange={(e) => setIdentityToken(e.target.value)}
placeholder="Enter identity token..."
placeholder="Enter machine identity token..."
isError={Boolean(errors.identityToken)}
/>
{errors.identityToken && <p className="mt-1 text-sm text-red">{errors.identityToken}</p>}
@@ -325,15 +325,15 @@ export const GatewayCliDeploymentMethod = () => {
className="mr-2"
>
<div className="flex items-center">
<span>Automatically enable token auth and generate a token for identity</span>
<span>Automatically enable token auth and generate a token for machine identity</span>
<Tooltip
className="max-w-md"
content={
<>
Token authentication will be automatically enabled for the selected identity if
it isn&apos;t already configured. By default, it will be configured to allow all
IP addresses with a token TTL of 30 days. You can manage these settings in
Access Control.
Token authentication will be automatically enabled for the selected machine
identity if it isn&apos;t already configured. By default, it will be configured
to allow all IP addresses with a token TTL of 30 days. You can manage these
settings in Access Control.
<br />
<br />A token will automatically be generated to be used with the CLI command.
</>
@@ -55,10 +55,10 @@ const formSchemaWithIdentity = baseFormSchema.extend({
id: z.string(),
name: z.string()
},
{ required_error: "Identity is required" }
{ required_error: "Machine identity is required" }
)
.nullable()
.refine((val) => val !== null, { message: "Identity is required" })
.refine((val) => val !== null, { message: "Machine identity is required" })
});
const formSchemaWithToken = baseFormSchema.extend({
@@ -297,8 +297,8 @@ export const GatewayCliSystemdDeploymentMethod = () => {
{canCreateToken && autogenerateToken ? (
<>
<FormLabel
label="Identity"
tooltipText="The identity that your gateway will use for authentication."
label="Machine Identity"
tooltipText="The machine identity that your gateway will use for authentication."
className="mt-4"
/>
<FilterableSelect
@@ -312,7 +312,7 @@ export const GatewayCliSystemdDeploymentMethod = () => {
)
}
isLoading={isIdentitiesLoading}
placeholder="Select identity..."
placeholder="Select machine identity..."
options={identityMembershipOrgs.map((membership) => membership.identity)}
getOptionValue={(option) => option.id}
getOptionLabel={(option) => option.name}
@@ -322,14 +322,14 @@ export const GatewayCliSystemdDeploymentMethod = () => {
) : (
<>
<FormLabel
label="Identity Token"
tooltipText="The identity token that your gateway will use for authentication."
label="Machine Identity Token"
tooltipText="The machine identity token that your gateway will use for authentication."
className="mt-4"
/>
<Input
value={identityToken}
onChange={(e) => setIdentityToken(e.target.value)}
placeholder="Enter identity token..."
placeholder="Enter machine identity token..."
isError={Boolean(errors.identityToken)}
/>
{errors.identityToken && <p className="mt-1 text-sm text-red">{errors.identityToken}</p>}
@@ -347,15 +347,15 @@ export const GatewayCliSystemdDeploymentMethod = () => {
className="mr-2"
>
<div className="flex items-center">
<span>Automatically enable token auth and generate a token for identity</span>
<span>Automatically enable token auth and generate a token for machine identity</span>
<Tooltip
className="max-w-md"
content={
<>
Token authentication will be automatically enabled for the selected identity if
it isn&apos;t already configured. By default, it will be configured to allow all
IP addresses with a token TTL of 30 days. You can manage these settings in
Access Control.
Token authentication will be automatically enabled for the selected machine
identity if it isn&apos;t already configured. By default, it will be configured
to allow all IP addresses with a token TTL of 30 days. You can manage these
settings in Access Control.
<br />
<br />A token will automatically be generated to be used with the CLI command.
</>
@@ -41,10 +41,10 @@ const formSchemaWithIdentity = baseFormSchema.extend({
id: z.string(),
name: z.string()
},
{ required_error: "Identity is required" }
{ required_error: "Machine identity is required" }
)
.nullable()
.refine((val) => val !== null, { message: "Identity is required" })
.refine((val) => val !== null, { message: "Machine identity is required" })
});
const formSchemaWithToken = baseFormSchema.extend({
@@ -229,8 +229,8 @@ export const RelayCliDeploymentMethod = () => {
{canCreateToken && autogenerateToken ? (
<>
<FormLabel
label="Identity"
tooltipText="The identity that your relay will use for authentication."
label="Machine Identity"
tooltipText="The machine identity that your relay will use for authentication."
className="mt-4"
/>
<FilterableSelect
@@ -244,7 +244,7 @@ export const RelayCliDeploymentMethod = () => {
)
}
isLoading={isIdentitiesLoading}
placeholder="Select identity..."
placeholder="Select machine identity..."
options={identityMembershipOrgs.map((membership) => membership.identity)}
getOptionValue={(option) => option.id}
getOptionLabel={(option) => option.name}
@@ -254,14 +254,14 @@ export const RelayCliDeploymentMethod = () => {
) : (
<>
<FormLabel
label="Identity Token"
tooltipText="The identity token that your relay will use for authentication."
label="Machine Identity Token"
tooltipText="The machine identity token that your relay will use for authentication."
className="mt-4"
/>
<Input
value={identityToken}
onChange={(e) => setIdentityToken(e.target.value)}
placeholder="Enter identity token..."
placeholder="Enter machine identity token..."
isError={Boolean(errors.identityToken)}
/>
{errors.identityToken && <p className="mt-1 text-sm text-red">{errors.identityToken}</p>}
@@ -279,15 +279,15 @@ export const RelayCliDeploymentMethod = () => {
className="mr-2"
>
<div className="flex items-center">
<span>Automatically enable token auth and generate a token for identity</span>
<span>Automatically enable token auth and generate a token for machine identity</span>
<Tooltip
className="max-w-md"
content={
<>
Token authentication will be automatically enabled for the selected identity if
it isn&apos;t already configured. By default, it will be configured to allow all
IP addresses with a token TTL of 30 days. You can manage these settings in
Access Control.
Token authentication will be automatically enabled for the selected machine
identity if it isn&apos;t already configured. By default, it will be configured
to allow all IP addresses with a token TTL of 30 days. You can manage these
settings in Access Control.
<br />
<br />A token will automatically be generated to be used with the CLI command.
</>
@@ -41,10 +41,10 @@ const formSchemaWithIdentity = baseFormSchema.extend({
id: z.string(),
name: z.string()
},
{ required_error: "Identity is required" }
{ required_error: "Machine identity is required" }
)
.nullable()
.refine((val) => val !== null, { message: "Identity is required" })
.refine((val) => val !== null, { message: "Machine identity is required" })
});
const formSchemaWithToken = baseFormSchema.extend({
@@ -270,8 +270,8 @@ export const RelayCliSystemdDeploymentMethod = () => {
{canCreateToken && autogenerateToken ? (
<>
<FormLabel
label="Identity"
tooltipText="The identity that your relay will use for authentication."
label="Machine Identity"
tooltipText="The machine identity that your relay will use for authentication."
className="mt-4"
/>
<FilterableSelect
@@ -285,7 +285,7 @@ export const RelayCliSystemdDeploymentMethod = () => {
)
}
isLoading={isIdentitiesLoading}
placeholder="Select identity..."
placeholder="Select machine identity..."
options={identityMembershipOrgs.map((membership) => membership.identity)}
getOptionValue={(option) => option.id}
getOptionLabel={(option) => option.name}
@@ -295,14 +295,14 @@ export const RelayCliSystemdDeploymentMethod = () => {
) : (
<>
<FormLabel
label="Identity Token"
tooltipText="The identity token that your relay will use for authentication."
label="Machine Identity Token"
tooltipText="The machine identity token that your relay will use for authentication."
className="mt-4"
/>
<Input
value={identityToken}
onChange={(e) => setIdentityToken(e.target.value)}
placeholder="Enter identity token..."
placeholder="Enter machine identity token..."
isError={Boolean(errors.identityToken)}
/>
{errors.identityToken && <p className="mt-1 text-sm text-red">{errors.identityToken}</p>}
@@ -320,15 +320,15 @@ export const RelayCliSystemdDeploymentMethod = () => {
className="mr-2"
>
<div className="flex items-center">
<span>Automatically enable token auth and generate a token for identity</span>
<span>Automatically enable token auth and generate a token for machine identity</span>
<Tooltip
className="max-w-md"
content={
<>
Token authentication will be automatically enabled for the selected identity if
it isn&apos;t already configured. By default, it will be configured to allow all
IP addresses with a token TTL of 30 days. You can manage these settings in
Access Control.
Token authentication will be automatically enabled for the selected machine
identity if it isn&apos;t already configured. By default, it will be configured
to allow all IP addresses with a token TTL of 30 days. You can manage these
settings in Access Control.
<br />
<br />A token will automatically be generated to be used with the CLI command.
</>
@@ -42,10 +42,10 @@ const formSchemaWithIdentity = baseFormSchema.extend({
id: z.string(),
name: z.string()
},
{ required_error: "Identity is required" }
{ required_error: "Machine identity is required" }
)
.nullable()
.refine((val) => val !== null, { message: "Identity is required" })
.refine((val) => val !== null, { message: "Machine identity is required" })
});
const formSchemaWithToken = baseFormSchema.extend({
@@ -349,8 +349,8 @@ resource "aws_eip_association" "eip_assoc" {
{canCreateToken && autogenerateToken ? (
<>
<FormLabel
label="Identity"
tooltipText="The identity that your relay will use for authentication."
label="Machine Identity"
tooltipText="The machine identity that your relay will use for authentication."
className="mt-4"
/>
<FilterableSelect
@@ -364,7 +364,7 @@ resource "aws_eip_association" "eip_assoc" {
)
}
isLoading={isIdentitiesLoading}
placeholder="Select identity..."
placeholder="Select machine identity..."
options={identityMembershipOrgs.map((membership) => membership.identity)}
getOptionValue={(option) => option.id}
getOptionLabel={(option) => option.name}
@@ -374,14 +374,14 @@ resource "aws_eip_association" "eip_assoc" {
) : (
<>
<FormLabel
label="Identity Token"
tooltipText="The identity token that your relay will use for authentication."
label="Machine Identity Token"
tooltipText="The machine identity token that your relay will use for authentication."
className="mt-4"
/>
<Input
value={identityToken}
onChange={(e) => setIdentityToken(e.target.value)}
placeholder="Enter identity token..."
placeholder="Enter machine identity token..."
isError={Boolean(errors.identityToken)}
/>
{errors.identityToken && <p className="mt-1 text-sm text-red">{errors.identityToken}</p>}
@@ -399,15 +399,15 @@ resource "aws_eip_association" "eip_assoc" {
className="mr-2"
>
<div className="flex items-center">
<span>Automatically enable token auth and generate a token for identity</span>
<span>Automatically enable token auth and generate a token for machine identity</span>
<Tooltip
className="max-w-md"
content={
<>
Token authentication will be automatically enabled for the selected identity if
it isn&apos;t already configured. By default, it will be configured to allow all
IP addresses with a token TTL of 30 days. You can manage these settings in
Access Control.
Token authentication will be automatically enabled for the selected machine
identity if it isn&apos;t already configured. By default, it will be configured
to allow all IP addresses with a token TTL of 30 days. You can manage these
settings in Access Control.
<br />
<br />A token will automatically be generated to be used with the CLI command.
</>
@@ -81,7 +81,7 @@ export const ProjectsPage = () => {
</Helmet>
<PageHeader
scope={isSubOrganization ? "namespace" : "org"}
title="Overview"
title={`${isSubOrganization ? "Sub-Organization" : "Organization"} Overview`}
description="Your team's complete security toolkit - organized and ready when you need them."
/>
{projectListView === ProjectListView.MyProjects ? (
@@ -20,7 +20,7 @@ export const SettingsPage = () => {
<PageHeader
scope={isSubOrganization ? "namespace" : "org"}
description="Configure organization-wide settings"
title={t("settings.org.title")}
title={isSubOrganization ? "Sub-Organization Settings" : "Organization Settings"}
/>
<OrgTabGroup />
</div>
@@ -112,7 +112,7 @@ const Page = withPermission(
className="mb-4 flex items-center gap-x-2 text-sm text-mineshaft-400"
>
<FontAwesomeIcon icon={faChevronLeft} />
Users
Organization Users
</Link>
<PageHeader
scope={isSubOrganization ? "namespace" : "org"}
@@ -1,13 +1,18 @@
import { Helmet } from "react-helmet";
import { useTranslation } from "react-i18next";
import { Link } from "@tanstack/react-router";
import { InfoIcon } from "lucide-react";
import { PageHeader, Tab, TabList, TabPanel, Tabs } from "@app/components/v2";
import { useOrganization } from "@app/context";
import { ProjectType } from "@app/hooks/api/projects/types";
import { ProjectGeneralTab } from "@app/pages/project/SettingsPage/components/ProjectGeneralTab";
export const SettingsPage = () => {
const { t } = useTranslation();
const { currentOrg } = useOrganization();
return (
<div className="flex h-full w-full justify-center bg-bunker-800 text-white">
<Helmet>
@@ -16,9 +21,19 @@ export const SettingsPage = () => {
<div className="w-full max-w-8xl">
<PageHeader
scope={ProjectType.PAM}
title="Settings"
title="Project Settings"
description="Configure your PAM project."
/>
>
<Link
to="/organizations/$orgId/settings"
params={{
orgId: currentOrg.id
}}
className="flex items-center gap-x-1.5 text-xs whitespace-nowrap text-neutral hover:underline"
>
<InfoIcon size={12} /> Looking for organization settings?
</Link>
</PageHeader>
<Tabs orientation="vertical" defaultValue="tab-project-general">
<TabList>
<Tab variant="project" value="tab-project-general">
@@ -1,6 +1,7 @@
import { Helmet } from "react-helmet";
import { useTranslation } from "react-i18next";
import { useNavigate, useSearch } from "@tanstack/react-router";
import { Link, useNavigate, useSearch } from "@tanstack/react-router";
import { InfoIcon } from "lucide-react";
import { PageHeader, Tab, TabList, TabPanel, Tabs } from "@app/components/v2";
import { useOrganization, useProject } from "@app/context";
@@ -43,9 +44,19 @@ const Page = () => {
<div className="mx-auto mb-6 w-full max-w-8xl">
<PageHeader
scope={currentProject.type}
title="Access Control"
description="Manage fine-grained access for users, groups, roles, and identities within your project resources."
/>
title="Project Access Control"
description="Manage fine-grained access for users, groups, roles, and machine identities within your project resources."
>
<Link
to="/organizations/$orgId/access-management"
params={{
orgId: currentOrg.id
}}
className="flex items-center gap-x-1.5 text-xs whitespace-nowrap text-neutral hover:underline"
>
<InfoIcon size={12} /> Looking for organization access control?
</Link>
</PageHeader>
<Tabs orientation="vertical" value={selectedTab} onValueChange={updateSelectedTab}>
<TabList>
<Tab variant="project" value={ProjectAccessControlTabs.Member}>
@@ -55,7 +66,7 @@ const Page = () => {
Groups
</Tab>
<Tab variant="project" value={ProjectAccessControlTabs.Identities}>
Identities
Machine Identities
</Tab>
{isSecretManager && (
<Tab variant="project" value={ProjectAccessControlTabs.ServiceTokens}>
@@ -59,19 +59,19 @@ export const GroupsSection = () => {
<div className="mb-6 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
<div className="mb-4 flex items-center justify-between">
<div className="flex items-center gap-x-2">
<p className="text-xl font-medium text-mineshaft-100">User Groups</p>
<p className="text-xl font-medium text-mineshaft-100">Project Groups</p>
<DocumentationLinkBadge href="https://infisical.com/docs/documentation/platform/groups#user-groups" />
</div>
<ProjectPermissionCan I={ProjectPermissionActions.Create} a={ProjectPermissionSub.Groups}>
{(isAllowed) => (
<Button
colorSchema="secondary"
variant="outline_bg"
type="submit"
leftIcon={<FontAwesomeIcon icon={faPlus} />}
onClick={() => handleAddGroupModal()}
isDisabled={!isAllowed}
>
Add Group
Add Group to Project
</Button>
)}
</ProjectPermissionCan>
@@ -122,7 +122,7 @@ export const GroupTable = ({ handlePopUpOpen }: Props) => {
value={search}
onChange={(e) => setSearch(e.target.value)}
leftIcon={<FontAwesomeIcon icon={faMagnifyingGlass} />}
placeholder="Search members..."
placeholder="Search project groups..."
/>
<TableContainer className="mt-4">
<Table>
@@ -143,7 +143,7 @@ export const GroupTable = ({ handlePopUpOpen }: Props) => {
</IconButton>
</div>
</Th>
<Th>Role</Th>
<Th>Project Role</Th>
<Th>Added on</Th>
<Th className="w-5" />
</Tr>
@@ -158,7 +158,7 @@ export const IdentityTab = withProjectPermission(
});
createNotification({
text: "Successfully deleted project identity",
text: "Successfully deleted project machine identity",
type: "success"
});
} else {
@@ -168,7 +168,7 @@ export const IdentityTab = withProjectPermission(
});
createNotification({
text: "Successfully removed identity from project",
text: "Successfully removed machine identity from project",
type: "success"
});
}
@@ -197,7 +197,7 @@ export const IdentityTab = withProjectPermission(
<div className="mb-6 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
<div className="mb-4 flex items-center justify-between">
<div className="flex items-center gap-x-2">
<p className="text-xl font-medium text-mineshaft-100">Identities</p>
<p className="text-xl font-medium text-mineshaft-100">Project Machine Identities</p>
<DocumentationLinkBadge href="https://infisical.com/docs/documentation/platform/identities/machine-identities" />
</div>
<div className="flex items-center">
@@ -212,7 +212,7 @@ export const IdentityTab = withProjectPermission(
onClick={() => handlePopUpOpen("createIdentity")}
isDisabled={!isAllowed}
>
Create Identity
Add Machine Identity to Project
</Button>
)}
</ProjectPermissionCan>
@@ -223,7 +223,7 @@ export const IdentityTab = withProjectPermission(
value={search}
onChange={(e) => setSearch(e.target.value)}
leftIcon={<FontAwesomeIcon icon={faMagnifyingGlass} />}
placeholder="Search identities by name..."
placeholder="Search project machine identities by name..."
/>
<TableContainer>
<Table>
@@ -251,7 +251,7 @@ export const IdentityTab = withProjectPermission(
</IconButton>
</div>
</Th>
<Th className="w-1/3">Role</Th>
<Th className="w-1/3">Project Role</Th>
<Th>Managed by</Th>
<Th className="w-5">{isFetching ? <Spinner size="xs" /> : null}</Th>
</Tr>
@@ -454,7 +454,9 @@ export const IdentityTab = withProjectPermission(
});
}}
>
{identityProjectId ? "Delete Identity" : "Remove From Project"}
{identityProjectId
? "Delete Machine Identity"
: "Remove From Project"}
</DropdownMenuItem>
)}
</ProjectPermissionCan>
@@ -474,7 +476,7 @@ export const IdentityTab = withProjectPermission(
<Td colSpan={3}>
<Blur
className="w-min"
tooltipText="You do not have permission to read this identity."
tooltipText="You do not have permission to view this machine identity."
/>
</Td>
</Tr>
@@ -497,8 +499,8 @@ export const IdentityTab = withProjectPermission(
<EmptyState
title={
debouncedSearch.trim().length > 0
? "No identities match search filter"
: "No identities have been added to this project"
? "No machine identities match search filter"
: "No machine identities have been added to this project"
}
icon={faServer}
/>
@@ -513,8 +515,8 @@ export const IdentityTab = withProjectPermission(
>
<ModalContent
bodyClassName="overflow-visible"
title="Add Project Identity"
subTitle="Create a new identity or assign an existing identity"
title="Add Project Machine Identity"
subTitle="Create a new machine identity or assign an existing one"
>
<AnimatePresence mode="wait">
{wizardStep === WizardSteps.SelectAction && (
@@ -538,11 +540,11 @@ export const IdentityTab = withProjectPermission(
>
<div className="flex items-center gap-2">
<PlusIcon size="1rem" />
<div>Create New Identity</div>
<div>Create Machine Identity</div>
</div>
<div className="mt-2 text-xs text-mineshaft-300">
Create a new machine identity specifically for this project. This identity
will be managed at the project-level.
Create a new machine identity specifically for this project. This machine
identity will be managed at the project-level.
</div>
</div>
<div
@@ -558,11 +560,11 @@ export const IdentityTab = withProjectPermission(
>
<div className="flex items-center gap-2">
<LinkIcon size="1rem" />
<div>Assign Existing Identity</div>
<div>Assign Existing Machine Identity</div>
</div>
<div className="mt-2 text-xs text-mineshaft-300">
Assign an existing identity from your organization. The identity will continue
to be managed at its original scope.
Assign an existing machine identity from your organization. The machine
identity will continue to be managed at its original scope.
</div>
</div>
</motion.div>
@@ -138,7 +138,7 @@ export const ProjectIdentityModal = ({ onClose, identity }: ContentProps) => {
}
createNotification({
text: `Successfully ${isUpdate ? "updated" : "created"} project identity`,
text: `Successfully ${isUpdate ? "updated" : "created"} project machine identity`,
type: "success"
});
@@ -148,7 +148,7 @@ export const ProjectIdentityModal = ({ onClose, identity }: ContentProps) => {
const error = err as any;
const text =
error?.response?.data?.message ??
`Failed to ${isUpdate ? "update" : "create"} project identity`;
`Failed to ${isUpdate ? "update" : "create"} project machine identity`;
createNotification({
text,
@@ -233,9 +233,7 @@ export const ProjectIdentityModal = ({ onClose, identity }: ContentProps) => {
/>
</div>
<div className="grow">
{i === 0 && (
<FormLabel label="Value" className="text-xs text-mineshaft-400" isOptional />
)}
{i === 0 && <FormLabel label="Value" className="text-xs text-mineshaft-400" />}
<Controller
control={control}
name={`metadata.${i}.value`}
@@ -83,7 +83,7 @@ export const ProjectLinkIdentityModal = ({ handlePopUpToggle }: Props) => {
});
createNotification({
text: "Successfully added identity to project",
text: "Successfully added machine identity to project",
type: "success"
});
@@ -114,11 +114,11 @@ export const ProjectLinkIdentityModal = ({ handlePopUpToggle }: Props) => {
control={control}
name="identity"
render={({ field: { onChange, value }, fieldState: { error } }) => (
<FormControl label="Identity" errorText={error?.message} isError={Boolean(error)}>
<FormControl label="Machine Identity" errorText={error?.message} isError={Boolean(error)}>
<FilterableSelect
value={value}
onChange={onChange}
placeholder="Select identity..."
placeholder="Select machine identity..."
// onInputChange={setSearchValue}
options={filteredIdentityMembershipOrgs.map((membership) => ({
name: membership.name,
@@ -49,19 +49,19 @@ export const MembersSection = () => {
<div className="mb-6 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
<div className="mb-4 flex items-center justify-between">
<div className="flex items-center gap-x-2">
<p className="text-xl font-medium text-mineshaft-100">Users</p>
<p className="text-xl font-medium text-mineshaft-100">Project Users</p>
<DocumentationLinkBadge href="https://infisical.com/docs/documentation/platform/identities/user-identities" />
</div>
<ProjectPermissionCan I={ProjectPermissionActions.Create} a={ProjectPermissionSub.Member}>
{(isAllowed) => (
<Button
colorSchema="secondary"
variant="outline_bg"
type="submit"
leftIcon={<FontAwesomeIcon icon={faPlus} />}
onClick={() => handlePopUpOpen("addMember")}
isDisabled={!isAllowed}
>
Add Member
Add Users to Project
</Button>
)}
</ProjectPermissionCan>
@@ -208,7 +208,7 @@ export const MembersTable = ({ handlePopUpOpen }: Props) => {
</DropdownSubMenuTrigger>
<DropdownSubMenuContent className="max-h-80 thin-scrollbar overflow-y-auto rounded-l-none">
<DropdownMenuLabel className="sticky top-0 bg-mineshaft-900">
Apply Roles to Filter Users
Filter Project Users by Role
</DropdownMenuLabel>
{projectRoles?.map(({ id, slug, name }) => (
<DropdownMenuItem
@@ -237,7 +237,7 @@ export const MembersTable = ({ handlePopUpOpen }: Props) => {
value={search}
onChange={(e) => setSearch(e.target.value)}
leftIcon={<FontAwesomeIcon icon={faMagnifyingGlass} />}
placeholder="Search members..."
placeholder="Search project users..."
/>
</div>
<TableContainer className="mt-4">
@@ -282,7 +282,7 @@ export const MembersTable = ({ handlePopUpOpen }: Props) => {
</IconButton>
</div>
</Th>
<Th>Role</Th>
<Th>Project Role</Th>
<Th className="w-5" />
</Tr>
</THead>
@@ -462,9 +462,7 @@ export const MembersTable = ({ handlePopUpOpen }: Props) => {
)}
{!isMembersLoading && !filteredUsers?.length && (
<EmptyState
title={
members.length ? "No project members match search..." : "No project members found"
}
title={members.length ? "No project users match search..." : "No project users found"}
icon={members.length ? faSearch : faUsers}
/>
)}
@@ -170,13 +170,13 @@ export const ProjectRoleList = () => {
<ProjectPermissionCan I={ProjectPermissionActions.Create} a={ProjectPermissionSub.Role}>
{(isAllowed) => (
<Button
colorSchema="secondary"
variant="outline_bg"
type="submit"
leftIcon={<FontAwesomeIcon icon={faPlus} />}
onClick={() => handlePopUpOpen("role")}
isDisabled={!isAllowed}
>
Add Role
Add Project Role
</Button>
)}
</ProjectPermissionCan>
@@ -57,7 +57,7 @@ export const ServiceTokenSection = withProjectPermission(
>
{(isAllowed) => (
<Button
colorSchema="secondary"
variant="outline_bg"
leftIcon={<FontAwesomeIcon icon={faPlus} />}
onClick={() => {
handlePopUpOpen("createAPIToken");
@@ -25,7 +25,7 @@ export const AppConnectionsPage = withProjectPermission(
<PageHeader
scope={currentProject.type}
className="w-full"
title="App Connections"
title="Project App Connections"
description="Manage project App Connections"
/>
@@ -1,4 +1,6 @@
import { Helmet } from "react-helmet";
import { Link } from "@tanstack/react-router";
import { InfoIcon } from "lucide-react";
import { PageHeader } from "@app/components/v2";
import { useProject } from "@app/context";
@@ -17,9 +19,19 @@ export const AuditLogsPage = () => {
<div className="w-full max-w-8xl">
<PageHeader
scope={currentProject.type}
title="Audit logs"
title="Project Audit logs"
description="Audit logs for security and compliance teams to monitor information access."
/>
>
<Link
to="/organizations/$orgId/audit-logs"
params={{
orgId: currentProject.orgId
}}
className="flex items-center gap-x-1.5 text-xs whitespace-nowrap text-neutral hover:underline"
>
<InfoIcon size={12} /> Looking for organization audit logs?
</Link>
</PageHeader>
<LogsSection pageView project={currentProject} />
</div>
</div>
@@ -57,7 +57,7 @@ const Page = () => {
className="mb-4 flex items-center gap-x-2 text-sm text-mineshaft-400"
>
<FontAwesomeIcon icon={faChevronLeft} />
Groups
Project Groups
</Link>
<PageHeader
scope={currentProject.type}
@@ -92,7 +92,7 @@ const Page = () => {
onSuccess: () => {
createNotification({
type: "success",
text: "Identity privilege assumption has started"
text: "Machine identity privilege assumption has started"
});
const url = `${getProjectHomePage(currentProject.type, currentProject.environments)}${isSubOrganization && isNonScopedIdentity ? `?subOrganization=${currentOrg.slug}` : ""}`;
window.location.assign(
@@ -109,7 +109,7 @@ const Page = () => {
projectId
});
createNotification({
text: "Successfully removed identity from project",
text: "Successfully removed machine identity from project",
type: "success"
});
handlePopUpClose("deleteIdentity");
@@ -149,12 +149,12 @@ const Page = () => {
className="mb-4 flex items-center gap-x-2 text-sm text-mineshaft-400"
>
<FontAwesomeIcon icon={faChevronLeft} />
Identities
Project Machine Identities
</Link>
<PageHeader
scope={currentProject.type}
title={identityMembershipDetails?.identity?.name}
description={`Identity ${isProjectIdentity ? "created" : "added"} on ${identityMembershipDetails?.createdAt && formatRelative(new Date(identityMembershipDetails?.createdAt || ""), new Date())}`}
description={`Machine identity ${isProjectIdentity ? "created" : "added"} on ${identityMembershipDetails?.createdAt && formatRelative(new Date(identityMembershipDetails?.createdAt || ""), new Date())}`}
className={!isProjectIdentity ? "mb-4" : undefined}
>
<div className="flex items-center gap-2">
@@ -177,7 +177,7 @@ const Page = () => {
identityId: identityMembershipDetails?.identity.id
})}
renderTooltip
allowedLabel="Assume privileges of the user"
allowedLabel="Assume privileges of the machine identity"
passThrough={false}
>
{(isAllowed) => (
@@ -209,7 +209,7 @@ const Page = () => {
isLoading={isDeletingIdentity}
onClick={() => handlePopUpOpen("deleteIdentity")}
>
Remove Identity
Remove Machine Identity
</Button>
)}
</ProjectPermissionCan>
@@ -219,7 +219,7 @@ const Page = () => {
{!isProjectIdentity && (
<Alert hideTitle iconClassName="text-info" className="mb-4 border-info/50 bg-info/10">
<AlertDescription>
This identity is managed by your organization.{" "}
This machine identity is managed by your organization.{" "}
<OrgPermissionCan
I={OrgPermissionIdentityActions.Read}
an={OrgPermissionSubjects.Identity}
@@ -234,7 +234,7 @@ const Page = () => {
}}
>
<span className="cursor-pointer text-info underline underline-offset-2">
Click here to manage identity.
Click here to manage machine identity.
</span>
</Link>
) : null
@@ -286,15 +286,15 @@ const Page = () => {
<ConfirmActionModal
isOpen={popUp.assumePrivileges.isOpen}
confirmKey="assume"
title="Do you want to assume privileges of this identity?"
subTitle="This will set your privileges to those of the identity for the next hour."
title="Do you want to assume privileges of this machine identity?"
subTitle="This will set your privileges to those of the machine identity for the next hour."
onChange={(isOpen) => handlePopUpToggle("assumePrivileges", isOpen)}
onConfirmed={handleAssumePrivileges}
buttonText="Confirm"
/>
</>
) : (
<EmptyState title="Error: Unable to find the identity." className="py-12" />
<EmptyState title="Error: Unable to find the machine identity." className="py-12" />
)}
</div>
);
@@ -235,7 +235,10 @@ export const IdentityProjectAdditionalPrivilegeSection = ({ identityMembershipDe
</TBody>
</Table>
{!isPending && !identityProjectPrivileges?.length && (
<EmptyState title="This identity has no additional privileges" icon={faFolder} />
<EmptyState
title="This machine identity has no additional privileges"
icon={faFolder}
/>
)}
</TableContainer>
</div>
@@ -69,7 +69,7 @@ export const ProjectIdentityDetailsSection = ({ identity, isOrgIdentity, members
} catch {
createNotification({
type: "error",
text: "Failed to delete project identity"
text: "Failed to delete project machine identity"
});
}
};
@@ -77,7 +77,7 @@ export const ProjectIdentityDetailsSection = ({ identity, isOrgIdentity, members
return (
<div className="rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
<div className="flex items-center justify-between border-b border-mineshaft-400 pb-4">
<h3 className="text-lg font-medium text-mineshaft-100">Identity Details</h3>
<h3 className="text-lg font-medium text-mineshaft-100">Details</h3>
<DropdownMenu>
{!isOrgIdentity && (
<DropdownMenuTrigger asChild>
@@ -114,7 +114,7 @@ export const ProjectIdentityDetailsSection = ({ identity, isOrgIdentity, members
}}
disabled={!isAllowed}
>
Edit Identity
Edit Machine Identity
</DropdownMenuItem>
)}
</ProjectPermissionCan>
@@ -137,7 +137,7 @@ export const ProjectIdentityDetailsSection = ({ identity, isOrgIdentity, members
icon={<FontAwesomeIcon icon={faTrash} />}
disabled={!isAllowed}
>
Delete Identity
Delete Machine Identity
</DropdownMenuItem>
)}
</ProjectPermissionCan>
@@ -146,7 +146,7 @@ export const ProjectIdentityDetailsSection = ({ identity, isOrgIdentity, members
</div>
<div className="pt-4">
<div className="mb-4">
<p className="text-sm font-medium text-mineshaft-300">Identity ID</p>
<p className="text-sm font-medium text-mineshaft-300">Machine Identity ID</p>
<div className="group flex align-top">
<p className="text-sm break-all text-mineshaft-300">{identity.id}</p>
<div className="opacity-0 transition-opacity duration-300 group-hover:opacity-100">
@@ -128,7 +128,7 @@ export const Page = () => {
className="mb-4 flex items-center gap-x-2 text-sm text-mineshaft-400"
>
<FontAwesomeIcon icon={faChevronLeft} />
Users
Project Users
</Link>
<PageHeader
scope={currentProject.type}
@@ -104,7 +104,7 @@ const Page = () => {
className="mb-4 flex items-center gap-x-2 text-sm text-mineshaft-400"
>
<FontAwesomeIcon icon={faChevronLeft} />
Roles
Project Roles
</Link>
<PageHeader
scope={currentProject.type}
@@ -57,7 +57,7 @@ export const IntegrationsListPage = () => {
<div className="mb-8">
<PageHeader
scope={ProjectType.SecretManager}
title="Integrations"
title="Project Integrations"
description="Manage integrations with third-party services."
/>
<Tabs orientation="vertical" value={selectedTab} onValueChange={updateSelectedTab}>
@@ -10,6 +10,8 @@ import {
faArrowRight,
faArrowRightToBracket,
faArrowUp,
faCheck,
faCopy,
faFilter,
faFingerprint,
faFolder,
@@ -79,6 +81,7 @@ import {
usePopUp,
useResetPageHelper,
useResizableHeaderHeight,
useTimedReset,
useToggle
} from "@app/hooks";
import {
@@ -194,6 +197,15 @@ export const OverviewPage = () => {
}
};
const [copiedSlug, , setCopiedSlug] = useTimedReset<string>({
initialState: ""
});
const copyToClipboard = (value: string, slug: string) => {
navigator.clipboard.writeText(value);
setCopiedSlug(slug);
};
const [filter, setFilter] = useState<Filter>(DEFAULT_FILTER_STATE);
const [filterHistory, setFilterHistory] = useState<
Map<string, { filter: Filter; searchFilter: string }>
@@ -921,12 +933,12 @@ export const OverviewPage = () => {
<div className="flex w-full items-baseline justify-between">
<PageHeader
scope={ProjectType.SecretManager}
title="Overview"
title="Project Overview"
description={
<p className="text-md text-bunker-300">
Inject your secrets using
<a
className="ml-1 text-mineshaft-300 underline decoration-primary-800 underline-offset-4 duration-200 hover:text-mineshaft-100 hover:decoration-primary-600"
className="ml-1 text-mineshaft-200 underline decoration-mineshaft-400/65 underline-offset-3 duration-200 hover:text-mineshaft-100 hover:decoration-primary-600"
href="https://infisical.com/docs/cli/overview"
target="_blank"
rel="noopener noreferrer"
@@ -935,7 +947,7 @@ export const OverviewPage = () => {
</a>
,
<a
className="ml-1 text-mineshaft-300 underline decoration-primary-800 underline-offset-4 duration-200 hover:text-mineshaft-100 hover:decoration-primary-600"
className="ml-1 text-mineshaft-200 underline decoration-mineshaft-400/65 underline-offset-3 duration-200 hover:text-mineshaft-100 hover:decoration-primary-600"
href="https://infisical.com/docs/documentation/getting-started/api"
target="_blank"
rel="noopener noreferrer"
@@ -944,7 +956,7 @@ export const OverviewPage = () => {
</a>
,
<a
className="ml-1 text-mineshaft-300 underline decoration-primary-800 underline-offset-4 duration-200 hover:text-mineshaft-100 hover:decoration-primary-600"
className="ml-1 text-mineshaft-200 underline decoration-mineshaft-400/65 underline-offset-3 duration-200 hover:text-mineshaft-100 hover:decoration-primary-600"
href="https://infisical.com/docs/sdks/overview"
target="_blank"
rel="noopener noreferrer"
@@ -953,7 +965,7 @@ export const OverviewPage = () => {
</a>
, and
<a
className="ml-1 text-mineshaft-300 underline decoration-primary-800 underline-offset-4 duration-200 hover:text-mineshaft-100 hover:decoration-primary-600"
className="ml-1 text-mineshaft-200 underline decoration-mineshaft-400/65 underline-offset-3 duration-200 hover:text-mineshaft-100 hover:decoration-primary-600"
href="https://infisical.com/docs/documentation/getting-started/introduction"
target="_blank"
rel="noopener noreferrer"
@@ -1324,19 +1336,33 @@ export const OverviewPage = () => {
>
<Tooltip
content={
collapseEnvironments ? (
<p className="whitespace-break-spaces">{name}</p>
) : (
""
)
<div className="flex flex-col gap-2">
{collapseEnvironments ? (
<p className="whitespace-break-spaces text-mineshaft-300">{name}</p>
) : (
""
)}
<div className="flex items-center gap-2">
<p className="text-xs text-mineshaft-300">{slug}</p>
<IconButton
variant="plain"
colorSchema="secondary"
ariaLabel="Copy environment slug"
onClick={() => copyToClipboard(slug, slug)}
>
<FontAwesomeIcon icon={copiedSlug === slug ? faCheck : faCopy} />
</IconButton>
</div>
</div>
}
side="bottom"
sideOffset={-1}
align="end"
sideOffset={5}
align="center"
className="max-w-xl text-xs normal-case"
rootProps={{
disableHoverableContent: true
disableHoverableContent: false
}}
key={`tooltip-${name}-${index + 1}`}
>
<div
className={twMerge(
@@ -1,5 +1,7 @@
import { Helmet } from "react-helmet";
import { useTranslation } from "react-i18next";
import { Link } from "@tanstack/react-router";
import { InfoIcon } from "lucide-react";
import { PageHeader, Tab, TabList, TabPanel, Tabs } from "@app/components/v2";
import { useProject } from "@app/context";
@@ -43,9 +45,19 @@ export const SettingsPage = () => {
<div className="w-full max-w-8xl">
<PageHeader
scope={ProjectType.SecretManager}
title="Settings"
title="Project Settings"
description="Configure your secret manager's encryption, environments, webhooks and other configurations."
/>
>
<Link
to="/organizations/$orgId/settings"
params={{
orgId: currentProject.orgId
}}
className="flex items-center gap-x-1.5 text-xs whitespace-nowrap text-neutral hover:underline"
>
<InfoIcon size={12} /> Looking for organization settings?
</Link>
</PageHeader>
<Tabs orientation="vertical" defaultValue={tabs[0].key}>
<TabList>
{tabs
@@ -1,5 +1,6 @@
import { Controller, useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import slugify from "@sindresorhus/slugify";
import { z } from "zod";
import { createNotification } from "@app/components/notifications";
@@ -31,7 +32,13 @@ type ContentProps = {
const Content = ({ onComplete }: ContentProps) => {
const { currentProject } = useProject();
const { mutateAsync, isPending } = useCreateWsEnvironment();
const { control, handleSubmit } = useForm<FormData>({
const {
control,
handleSubmit,
setValue,
getValues,
formState: { dirtyFields }
} = useForm<FormData>({
resolver: zodResolver(schema)
});
@@ -52,15 +59,28 @@ const Content = ({ onComplete }: ContentProps) => {
onComplete(env);
};
const handleEnvironmentNameChange = () => {
if (dirtyFields.environmentSlug) return;
const value = getValues("environmentName");
setValue("environmentSlug", slugify(value, { lowercase: true }));
};
return (
<form onSubmit={handleSubmit(onFormSubmit)}>
<Controller
control={control}
defaultValue=""
name="environmentName"
render={({ field, fieldState: { error } }) => (
render={({ field: { onChange, ...field }, fieldState: { error } }) => (
<FormControl label="Environment Name" isError={Boolean(error)} errorText={error?.message}>
<Input {...field} />
<Input
{...field}
onChange={(e) => {
onChange(e);
handleEnvironmentNameChange();
}}
/>
</FormControl>
)}
/>
@@ -1,9 +1,11 @@
import { Helmet } from "react-helmet";
import { useTranslation } from "react-i18next";
import { Link } from "@tanstack/react-router";
import { InfoIcon } from "lucide-react";
import { ProjectPermissionCan } from "@app/components/permissions";
import { PageHeader, Tab, TabList, TabPanel, Tabs } from "@app/components/v2";
import { ProjectPermissionSub } from "@app/context";
import { ProjectPermissionSub, useOrganization } from "@app/context";
import { ProjectPermissionSecretScanningConfigActions } from "@app/context/ProjectPermissionContext/types";
import { ProjectType } from "@app/hooks/api/projects/types";
import { ProjectGeneralTab } from "@app/pages/project/SettingsPage/components/ProjectGeneralTab";
@@ -12,6 +14,7 @@ import { ProjectScanningConfigTab } from "./components/ProjectScanningConfigTab"
export const SettingsPage = () => {
const { t } = useTranslation();
const { currentOrg } = useOrganization();
return (
<div className="flex h-full w-full justify-center bg-bunker-800 text-white">
@@ -21,9 +24,19 @@ export const SettingsPage = () => {
<div className="w-full max-w-8xl">
<PageHeader
scope={ProjectType.SecretScanning}
title="Settings"
title="Project Settings"
description="Configure your Secret Scanning product's configurations."
/>
>
<Link
to="/organizations/$orgId/settings"
params={{
orgId: currentOrg.id
}}
className="flex items-center gap-x-1.5 text-xs whitespace-nowrap text-neutral hover:underline"
>
<InfoIcon size={12} /> Looking for organization settings?
</Link>
</PageHeader>
<Tabs orientation="vertical" defaultValue="tab-project-general">
<TabList>
<Tab variant="project" value="tab-project-general">
@@ -1,9 +1,11 @@
import { Helmet } from "react-helmet";
import { useTranslation } from "react-i18next";
import { Link } from "@tanstack/react-router";
import { InfoIcon } from "lucide-react";
import { ProjectPermissionCan } from "@app/components/permissions";
import { PageHeader, Tab, TabList, TabPanel, Tabs } from "@app/components/v2";
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/context";
import { ProjectPermissionActions, ProjectPermissionSub, useOrganization } from "@app/context";
import { ProjectType } from "@app/hooks/api/projects/types";
import { ProjectGeneralTab } from "@app/pages/project/SettingsPage/components/ProjectGeneralTab";
@@ -12,6 +14,8 @@ import { ProjectSshTab } from "./components/ProjectSshTab";
export const SettingsPage = () => {
const { t } = useTranslation();
const { currentOrg } = useOrganization();
return (
<div className="flex h-full w-full justify-center bg-bunker-800 text-white">
<Helmet>
@@ -20,9 +24,19 @@ export const SettingsPage = () => {
<div className="w-full max-w-8xl">
<PageHeader
scope={ProjectType.SSH}
title="Settings"
title="Project Settings"
description="Configure your SSH product's configurations."
/>
>
<Link
to="/organizations/$orgId/settings"
params={{
orgId: currentOrg.id
}}
className="flex items-center gap-x-1.5 text-xs whitespace-nowrap text-neutral hover:underline"
>
<InfoIcon size={12} /> Looking for organization settings?
</Link>
</PageHeader>
<Tabs orientation="vertical" defaultValue="tab-project-general">
<TabList>
<Tab variant="project" value="tab-project-general">
+14
View File
@@ -23,6 +23,11 @@ const virtualRouteFileChangeReloadPlugin: PluginOption = {
export default defineConfig(({ mode }) => {
const env = loadEnv(mode, process.cwd());
const allowedHosts = env.VITE_ALLOWED_HOSTS?.split(",") ?? [];
const version = (
env.INFISICAL_PLATFORM_VERSION ||
env.VITE_INFISICAL_PLATFORM_VERSION ||
"0.0.1"
).replaceAll(".", "-");
return {
server: {
@@ -38,6 +43,15 @@ export default defineConfig(({ mode }) => {
// }
// }
},
build: {
rollupOptions: {
output: {
entryFileNames: `assets/[name]-${version}-[hash].js`,
chunkFileNames: `assets/[name]-${version}-[hash].js`,
assetFileNames: `assets/[name]-${version}-[hash].[ext]`
}
}
},
plugins: [
tsconfigPaths(),
nodePolyfills({