Merge remote-tracking branch 'origin/main' into misc/revamp-pki-apis

This commit is contained in:
Sheen Capadngan
2025-11-26 04:30:18 +08:00
79 changed files with 1769 additions and 525 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

@@ -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 = (
+4
View File
@@ -57,6 +57,7 @@ import { OCIConnectionMethod } from "@app/hooks/api/appConnections/types/oci-con
import { RailwayConnectionMethod } from "@app/hooks/api/appConnections/types/railway-connection";
import { RenderConnectionMethod } from "@app/hooks/api/appConnections/types/render-connection";
import { SupabaseConnectionMethod } from "@app/hooks/api/appConnections/types/supabase-connection";
import { DNSMadeEasyConnectionMethod } from "@app/hooks/api/appConnections/types/dns-made-easy-connection";
export const APP_CONNECTION_MAP: Record<
AppConnection,
@@ -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";
@@ -56,6 +57,7 @@ export * from "./camunda-connection";
export * from "./checkly-connection";
export * from "./chef-connection";
export * from "./cloudflare-connection";
export * from "./dns-made-easy-connection";
export * from "./databricks-connection";
export * from "./flyio-connection";
export * from "./gcp-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 {
@@ -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>
@@ -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,
@@ -210,6 +214,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
@@ -219,16 +228,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
@@ -240,6 +257,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) {
@@ -499,6 +521,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=""
@@ -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>
)}
@@ -58,7 +58,7 @@ export const AccessManagementPage = () => {
},
{
key: OrgAccessControlTabSections.Identities,
label: "Identities",
label: "Machine Identities",
isHidden: permission.cannot(
OrgPermissionIdentityActions.Read,
OrgPermissionSubjects.Identity
@@ -84,7 +84,7 @@ export const AccessManagementPage = () => {
<PageHeader
scope={isSubOrganization ? "namespace" : "org"}
title="Access Control"
description="Manage fine-grained access for users, groups, roles, and identities within your organization resources."
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">
@@ -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,7 @@ 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">Machine Identities</p>
<DocumentationLinkBadge href="https://infisical.com/docs/documentation/platform/identities/machine-identities" />
</div>
<div className="flex items-center">
@@ -116,7 +116,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 +129,7 @@ export const IdentitySection = withPermission(
}}
isDisabled={!isAllowed}
>
Create Identity
Add Machine Identity
</Button>
)}
</OrgPermissionCan>
@@ -141,7 +141,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 +152,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 +199,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 +228,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 +248,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 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>
@@ -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"
});
@@ -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"
});
}
@@ -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 { zodResolver } from "@hookform/resolvers/zod";
import { Controller, FormProvider, useForm } from "react-hook-form";
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>
);
};
@@ -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
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.
</>
@@ -44,7 +44,7 @@ const Page = () => {
<PageHeader
scope={currentProject.type}
title="Access Control"
description="Manage fine-grained access for users, groups, roles, and identities within your project resources."
description="Manage fine-grained access for users, groups, roles, and machine identities within your project resources."
/>
<Tabs orientation="vertical" value={selectedTab} onValueChange={updateSelectedTab}>
<TabList>
@@ -55,7 +55,7 @@ const Page = () => {
Groups
</Tab>
<Tab variant="project" value={ProjectAccessControlTabs.Identities}>
Identities
Machine Identities
</Tab>
{isSecretManager && (
<Tab variant="project" value={ProjectAccessControlTabs.ServiceTokens}>
@@ -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">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
</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 machine identities by name..."
/>
<TableContainer>
<Table>
@@ -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,
@@ -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,
@@ -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
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">
@@ -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 }>
@@ -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,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>
)}
/>