Merge remote-tracking branch 'origin' into ssh-non-interactive

This commit is contained in:
Tuan Dang
2025-04-25 09:31:39 -07:00
272 changed files with 4532 additions and 772 deletions
File diff suppressed because one or more lines are too long
@@ -14,6 +14,7 @@ import { GitHubSyncFields } from "./GitHubSyncFields";
import { HumanitecSyncFields } from "./HumanitecSyncFields";
import { TerraformCloudSyncFields } from "./TerraformCloudSyncFields";
import { VercelSyncFields } from "./VercelSyncFields";
import { WindmillSyncFields } from "./WindmillSyncFields";
export const SecretSyncDestinationFields = () => {
const { watch } = useFormContext<TSecretSyncForm>();
@@ -43,6 +44,8 @@ export const SecretSyncDestinationFields = () => {
return <CamundaSyncFields />;
case SecretSync.Vercel:
return <VercelSyncFields />;
case SecretSync.Windmill:
return <WindmillSyncFields />;
default:
throw new Error(`Unhandled Destination Config Field: ${destination}`);
}
@@ -0,0 +1,105 @@
import { Controller, useFormContext, useWatch } from "react-hook-form";
import { SingleValue } from "react-select";
import { faCircleInfo } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { SecretSyncConnectionField } from "@app/components/secret-syncs/forms/SecretSyncConnectionField";
import { FilterableSelect, FormControl, Input, Tooltip } from "@app/components/v2";
import {
TWindmillWorkspace,
useWindmillConnectionListWorkspaces
} from "@app/hooks/api/appConnections/windmill";
import { SecretSync } from "@app/hooks/api/secretSyncs";
import { TSecretSyncForm } from "../schemas";
export const WindmillSyncFields = () => {
const { control, setValue } = useFormContext<
TSecretSyncForm & { destination: SecretSync.Windmill }
>();
const connectionId = useWatch({ name: "connection.id", control });
const { data: workspaces, isLoading: isWorkspacesLoading } = useWindmillConnectionListWorkspaces(
connectionId,
{
enabled: Boolean(connectionId)
}
);
return (
<>
<SecretSyncConnectionField
onChange={() => {
setValue("destinationConfig.workspace", "");
}}
/>
<Controller
name="destinationConfig.workspace"
control={control}
render={({ field: { value, onChange }, fieldState: { error } }) => (
<FormControl
isError={Boolean(error)}
errorText={error?.message}
label="Workspace"
helperText={
<Tooltip
className="max-w-md"
content="Ensure the workspace exists in the connection's Windmill instance URL."
>
<div>
<span>Don&#39;t see the workspace you&#39;re looking for?</span>{" "}
<FontAwesomeIcon icon={faCircleInfo} className="text-mineshaft-400" />
</div>
</Tooltip>
}
>
<FilterableSelect
menuPlacement="top"
isLoading={isWorkspacesLoading && Boolean(connectionId)}
isDisabled={!connectionId}
value={workspaces?.find((workspace) => workspace.name === value) ?? null}
onChange={(option) =>
onChange((option as SingleValue<TWindmillWorkspace>)?.name ?? null)
}
options={workspaces}
placeholder="Select a workspace..."
getOptionLabel={(option) => option.name}
getOptionValue={(option) => option.name}
/>
</FormControl>
)}
/>
<Controller
name="destinationConfig.path"
control={control}
render={({ field: { value, onChange }, fieldState: { error } }) => (
<FormControl
tooltipClassName="max-w-sm"
tooltipText={
<>
The workspace path where secrets should be synced to. Path must follow Windmill{" "}
<a
target="_blank"
rel="noopener noreferrer"
href="https://www.windmill.dev/docs/core_concepts/roles_and_permissions#path"
>
<span className="cursor-pointer underline decoration-primary underline-offset-2 hover:text-mineshaft-200">
owner path convention
</span>
.
</a>
</>
}
isError={Boolean(error)}
errorText={error?.message}
label="Path"
>
<Input value={value} onChange={onChange} placeholder="f/folder-name/" />
</FormControl>
)}
/>
</>
);
};
@@ -42,6 +42,7 @@ export const SecretSyncOptionsFields = ({ hideInitialSync }: Props) => {
case SecretSync.TerraformCloud:
case SecretSync.Camunda:
case SecretSync.Vercel:
case SecretSync.Windmill:
AdditionalSyncOptionsFieldsComponent = null;
break;
default:
@@ -24,6 +24,7 @@ import { GitHubSyncReviewFields } from "./GitHubSyncReviewFields";
import { HumanitecSyncReviewFields } from "./HumanitecSyncReviewFields";
import { TerraformCloudSyncReviewFields } from "./TerraformCloudSyncReviewFields";
import { VercelSyncReviewFields } from "./VercelSyncReviewFields";
import { WindmillSyncReviewFields } from "./WindmillSyncReviewFields";
export const SecretSyncReviewFields = () => {
const { watch } = useFormContext<TSecretSyncForm>();
@@ -84,6 +85,9 @@ export const SecretSyncReviewFields = () => {
case SecretSync.Vercel:
DestinationFieldsComponent = <VercelSyncReviewFields />;
break;
case SecretSync.Windmill:
DestinationFieldsComponent = <WindmillSyncReviewFields />;
break;
default:
throw new Error(`Unhandled Destination Review Fields: ${destination}`);
}
@@ -0,0 +1,18 @@
import { useFormContext } from "react-hook-form";
import { TSecretSyncForm } from "@app/components/secret-syncs/forms/schemas";
import { GenericFieldLabel } from "@app/components/v2";
import { SecretSync } from "@app/hooks/api/secretSyncs";
export const WindmillSyncReviewFields = () => {
const { watch } = useFormContext<TSecretSyncForm & { destination: SecretSync.Windmill }>();
const workspace = watch("destinationConfig.workspace");
const path = watch("destinationConfig.path");
return (
<>
<GenericFieldLabel label="Workspace">{workspace}</GenericFieldLabel>
<GenericFieldLabel label="Path">{path}</GenericFieldLabel>
</>
);
};
@@ -1,17 +1,17 @@
import { z } from "zod";
import { AwsSecretsManagerSyncDestinationSchema } from "@app/components/secret-syncs/forms/schemas/aws-secrets-manager-sync-destination-schema";
import { DatabricksSyncDestinationSchema } from "@app/components/secret-syncs/forms/schemas/databricks-sync-destination-schema";
import { GitHubSyncDestinationSchema } from "@app/components/secret-syncs/forms/schemas/github-sync-destination-schema";
import { AwsParameterStoreSyncDestinationSchema } from "./aws-parameter-store-sync-destination-schema";
import { AwsSecretsManagerSyncDestinationSchema } from "./aws-secrets-manager-sync-destination-schema";
import { AzureAppConfigurationSyncDestinationSchema } from "./azure-app-configuration-sync-destination-schema";
import { AzureKeyVaultSyncDestinationSchema } from "./azure-key-vault-sync-destination-schema";
import { CamundaSyncDestinationSchema } from "./camunda-sync-destination-schema";
import { DatabricksSyncDestinationSchema } from "./databricks-sync-destination-schema";
import { GcpSyncDestinationSchema } from "./gcp-sync-destination-schema";
import { GitHubSyncDestinationSchema } from "./github-sync-destination-schema";
import { HumanitecSyncDestinationSchema } from "./humanitec-sync-destination-schema";
import { TerraformCloudSyncDestinationSchema } from "./terraform-cloud-destination-schema";
import { VercelSyncDestinationSchema } from "./vercel-sync-destination-schema";
import { WindmillSyncDestinationSchema } from "./windmill-sync-destination-schema";
const SecretSyncUnionSchema = z.discriminatedUnion("destination", [
AwsParameterStoreSyncDestinationSchema,
@@ -24,7 +24,8 @@ const SecretSyncUnionSchema = z.discriminatedUnion("destination", [
HumanitecSyncDestinationSchema,
TerraformCloudSyncDestinationSchema,
CamundaSyncDestinationSchema,
VercelSyncDestinationSchema
VercelSyncDestinationSchema,
WindmillSyncDestinationSchema
]);
export const SecretSyncFormSchema = SecretSyncUnionSchema;
@@ -0,0 +1,21 @@
import { z } from "zod";
import { BaseSecretSyncSchema } from "@app/components/secret-syncs/forms/schemas/base-secret-sync-schema";
import { SecretSync } from "@app/hooks/api/secretSyncs";
export const WindmillSyncDestinationSchema = BaseSecretSyncSchema().merge(
z.object({
destination: z.literal(SecretSync.Windmill),
destinationConfig: z.object({
workspace: z.string().trim().min(1, "Workspace required"),
path: z
.string()
.trim()
.min(1, "Path required")
.regex(
/^([uf])\/([a-zA-Z0-9_-]+)(\/[a-zA-Z0-9_-]+)*\/$/,
'Invalid path - must follow Windmill path format. ex: "f/folder/path/"'
)
})
})
);
@@ -31,7 +31,7 @@ interface CheckPasswordParams {
* - Contains at least 1 number (0-9) or special character (emojis included)
* - Does not contain 3 repeat, consecutive characters
* - Does not contain any escape characters/sequences
* - Does not contain PII and/or low entropy data (eg. email address, URL, phone number, DoB, SSN, driver's license, passport)
* - Does not contain PII and/or low entropy data (eg. email address, URL, SSN)
* - Is not in a database of breached passwords
*
* The function returns whether or not the password [password]
@@ -20,18 +20,6 @@ export const lowEntropyRegexes = [
// URL (incl. subdomains, paths, top-level domains & query params)
/^(?:(?:https?|ftp):\/\/)?(?:\w+\.)?[a-zA-Z0-9.-]+\.(?:com|org|net|edu)(?:\/\S*)?(?:\?\S*)?$/,
// Date in various formats
/(\b\d{1,4}[-/.]?\d{1,2}[-/.]?\d{1,4}\b)|(\b\d{1,4}[-/.]?\w{3}[-/.]?\d{1,4}\b)/,
// Phone numbers (generalized)
/(?:\+(?:[1-9]\d{0,2})\s?)?(?:\(\d{1,4}\)\s?)?(?:\d[-.\s]?){5,}\d/,
// Passport numbers (generalized)
/\b(?:[A-Z0-9]{6,9}|[A-Z0-9]{8,9}|[A-Z0-9]{9}|[A-Z0-9]{10,11})\b/,
// Driver's license numbers (generalized)
/\b(?:[A-Z0-9]{7,10}|[A-Z0-9]{10,11}|[A-Z0-9]{7,10})\b/,
// US social security number
/\b\d{3}[-\s]?\d{2}[-\s]?\d{4}\b/
];
+8
View File
@@ -281,6 +281,14 @@ export const ROUTE_PATHS = Object.freeze({
"/cert-manager/$projectId/overview",
"/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/overview"
),
CertificateAuthoritiesPage: setRoute(
"/cert-manager/$projectId/certificate-authorities",
"/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/certificate-authorities"
),
AlertingPage: setRoute(
"/cert-manager/$projectId/alerting",
"/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/alerting"
),
PkiCollectionDetailsByIDPage: setRoute(
"/cert-manager/$projectId/pki-collections/$collectionId",
"/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/pki-collections/$collectionId"
+5 -1
View File
@@ -16,7 +16,8 @@ import {
PostgresConnectionMethod,
TAppConnection,
TerraformCloudConnectionMethod,
VercelConnectionMethod
VercelConnectionMethod,
WindmillConnectionMethod
} from "@app/hooks/api/appConnections/types";
export const APP_CONNECTION_MAP: Record<
@@ -41,6 +42,7 @@ export const APP_CONNECTION_MAP: Record<
[AppConnection.Postgres]: { name: "PostgreSQL", image: "Postgres.png" },
[AppConnection.MsSql]: { name: "Microsoft SQL Server", image: "MsSql.png" },
[AppConnection.Camunda]: { name: "Camunda", image: "Camunda.png" },
[AppConnection.Windmill]: { name: "Windmill", image: "Windmill.png" },
[AppConnection.Auth0]: { name: "Auth0", image: "Auth0.png", size: 40 }
};
@@ -69,6 +71,8 @@ export const getAppConnectionMethodDetails = (method: TAppConnection["method"])
case PostgresConnectionMethod.UsernameAndPassword:
case MsSqlConnectionMethod.UsernameAndPassword:
return { name: "Username & Password", icon: faLock };
case WindmillConnectionMethod.AccessToken:
return { name: "Access Token", icon: faKey };
case Auth0ConnectionMethod.ClientCredentials:
return { name: "Client Credentials", icon: faServer };
default:
+6 -1
View File
@@ -35,6 +35,10 @@ export const SECRET_SYNC_MAP: Record<SecretSync, { name: string; image: string }
[SecretSync.Vercel]: {
name: "Vercel",
image: "Vercel.png"
},
[SecretSync.Windmill]: {
name: "Windmill",
image: "Windmill.png"
}
};
@@ -49,7 +53,8 @@ export const SECRET_SYNC_CONNECTION_MAP: Record<SecretSync, AppConnection> = {
[SecretSync.Humanitec]: AppConnection.Humanitec,
[SecretSync.TerraformCloud]: AppConnection.TerraformCloud,
[SecretSync.Camunda]: AppConnection.Camunda,
[SecretSync.Vercel]: AppConnection.Vercel
[SecretSync.Vercel]: AppConnection.Vercel,
[SecretSync.Windmill]: AppConnection.Windmill
};
export const SECRET_SYNC_INITIAL_SYNC_BEHAVIOR_MAP: Record<
@@ -11,5 +11,6 @@ export enum AppConnection {
Postgres = "postgres",
MsSql = "mssql",
Camunda = "camunda",
Windmill = "windmill",
Auth0 = "auth0"
}
@@ -59,6 +59,10 @@ export type TCamundaConnectionOption = TAppConnectionOptionBase & {
app: AppConnection.Camunda;
};
export type TWindmillConnectionOption = TAppConnectionOptionBase & {
app: AppConnection.Windmill;
};
export type TAuth0ConnectionOption = TAppConnectionOptionBase & {
app: AppConnection.Auth0;
};
@@ -76,6 +80,7 @@ export type TAppConnectionOption =
| TPostgresConnectionOption
| TMsSqlConnectionOption
| TCamundaConnectionOption
| TWindmillConnectionOption
| TAuth0ConnectionOption;
export type TAppConnectionOptionMap = {
@@ -91,5 +96,6 @@ export type TAppConnectionOptionMap = {
[AppConnection.Postgres]: TPostgresConnectionOption;
[AppConnection.MsSql]: TMsSqlConnectionOption;
[AppConnection.Camunda]: TCamundaConnectionOption;
[AppConnection.Windmill]: TWindmillConnectionOption;
[AppConnection.Auth0]: TAuth0ConnectionOption;
};
@@ -13,6 +13,7 @@ import { TMsSqlConnection } from "./mssql-connection";
import { TPostgresConnection } from "./postgres-connection";
import { TTerraformCloudConnection } from "./terraform-cloud-connection";
import { TVercelConnection } from "./vercel-connection";
import { TWindmillConnection } from "./windmill-connection";
export * from "./auth0-connection";
export * from "./aws-connection";
@@ -27,6 +28,7 @@ export * from "./mssql-connection";
export * from "./postgres-connection";
export * from "./terraform-cloud-connection";
export * from "./vercel-connection";
export * from "./windmill-connection";
export type TAppConnection =
| TAwsConnection
@@ -41,6 +43,7 @@ export type TAppConnection =
| TPostgresConnection
| TMsSqlConnection
| TCamundaConnection
| TWindmillConnection
| TAuth0Connection;
export type TAvailableAppConnection = Pick<TAppConnection, "name" | "id">;
@@ -81,5 +84,6 @@ export type TAppConnectionMap = {
[AppConnection.Postgres]: TPostgresConnection;
[AppConnection.MsSql]: TMsSqlConnection;
[AppConnection.Camunda]: TCamundaConnection;
[AppConnection.Windmill]: TWindmillConnection;
[AppConnection.Auth0]: TAuth0Connection;
};
@@ -0,0 +1,14 @@
import { AppConnection } from "@app/hooks/api/appConnections/enums";
import { TRootAppConnection } from "@app/hooks/api/appConnections/types/root-connection";
export enum WindmillConnectionMethod {
AccessToken = "access-token"
}
export type TWindmillConnection = TRootAppConnection & { app: AppConnection.Windmill } & {
method: WindmillConnectionMethod.AccessToken;
credentials: {
accessToken: string;
instanceUrl?: string;
};
};
@@ -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 { TWindmillWorkspace } from "./types";
const windmillConnectionKeys = {
all: [...appConnectionKeys.all, "windmill"] as const,
listWorkspaces: (connectionId: string) =>
[...windmillConnectionKeys.all, "workspaces", connectionId] as const
};
export const useWindmillConnectionListWorkspaces = (
connectionId: string,
options?: Omit<
UseQueryOptions<
TWindmillWorkspace[],
unknown,
TWindmillWorkspace[],
ReturnType<typeof windmillConnectionKeys.listWorkspaces>
>,
"queryKey" | "queryFn"
>
) => {
return useQuery({
queryKey: windmillConnectionKeys.listWorkspaces(connectionId),
queryFn: async () => {
const { data } = await apiRequest.get<TWindmillWorkspace[]>(
`/api/v1/app-connections/windmill/${connectionId}/workspaces`
);
return data;
},
...options
});
};
@@ -0,0 +1,4 @@
export type TWindmillWorkspace = {
id: string;
name: string;
};
@@ -84,6 +84,7 @@ export const eventToNameMap: { [K in EventType]: string } = {
[EventType.ADD_PKI_COLLECTION_ITEM]: "Add PKI collection item",
[EventType.DELETE_PKI_COLLECTION_ITEM]: "Delete PKI collection item",
[EventType.ORG_ADMIN_ACCESS_PROJECT]: "Org admin accessed project",
[EventType.ORG_ADMIN_BYPASS_SSO]: "Org admin bypassed SSO enforcement",
[EventType.CREATE_CERTIFICATE_TEMPLATE]: "Create certificate template",
[EventType.UPDATE_CERTIFICATE_TEMPLATE]: "Update certificate template",
[EventType.DELETE_CERTIFICATE_TEMPLATE]: "Delete certificate template",
@@ -90,6 +90,7 @@ export enum EventType {
ADD_PKI_COLLECTION_ITEM = "add-pki-collection-item",
DELETE_PKI_COLLECTION_ITEM = "delete-pki-collection-item",
ORG_ADMIN_ACCESS_PROJECT = "org-admin-accessed-project",
ORG_ADMIN_BYPASS_SSO = "org-admin-bypassed-sso",
CREATE_CERTIFICATE_TEMPLATE = "create-certificate-template",
UPDATE_CERTIFICATE_TEMPLATE = "update-certificate-template",
DELETE_CERTIFICATE_TEMPLATE = "delete-certificate-template",
@@ -718,6 +718,11 @@ interface OrgAdminAccessProjectEvent {
}; // no metadata yet
}
interface OrgAdminBypassSSOEvent {
type: EventType.ORG_ADMIN_BYPASS_SSO;
metadata: Record<string, string>; // no metadata yet
}
interface CreateCertificateTemplate {
type: EventType.CREATE_CERTIFICATE_TEMPLATE;
metadata: {
@@ -885,6 +890,7 @@ export type Event =
| AddPkiCollectionItem
| DeletePkiCollectionItem
| OrgAdminAccessProjectEvent
| OrgAdminBypassSSOEvent
| CreateCertificateTemplate
| UpdateCertificateTemplate
| GetCertificateTemplate
+2 -1
View File
@@ -9,7 +9,8 @@ export enum SecretSync {
Humanitec = "humanitec",
TerraformCloud = "terraform-cloud",
Camunda = "camunda",
Vercel = "vercel"
Vercel = "vercel",
Windmill = "windmill"
}
export enum SecretSyncStatus {
@@ -1,17 +1,18 @@
import { SecretSync, SecretSyncImportBehavior } from "@app/hooks/api/secretSyncs";
import { TAwsParameterStoreSync } from "@app/hooks/api/secretSyncs/types/aws-parameter-store-sync";
import { TDatabricksSync } from "@app/hooks/api/secretSyncs/types/databricks-sync";
import { TGitHubSync } from "@app/hooks/api/secretSyncs/types/github-sync";
import { DiscriminativePick } from "@app/types";
import { TAwsParameterStoreSync } from "./aws-parameter-store-sync";
import { TAwsSecretsManagerSync } from "./aws-secrets-manager-sync";
import { TAzureAppConfigurationSync } from "./azure-app-configuration-sync";
import { TAzureKeyVaultSync } from "./azure-key-vault-sync";
import { TCamundaSync } from "./camunda-sync";
import { TDatabricksSync } from "./databricks-sync";
import { TGcpSync } from "./gcp-sync";
import { TGitHubSync } from "./github-sync";
import { THumanitecSync } from "./humanitec-sync";
import { TTerraformCloudSync } from "./terraform-cloud-sync";
import { TVercelSync } from "./vercel-sync";
import { TWindmillSync } from "./windmill-sync";
export type TSecretSyncOption = {
name: string;
@@ -30,7 +31,8 @@ export type TSecretSync =
| THumanitecSync
| TTerraformCloudSync
| TCamundaSync
| TVercelSync;
| TVercelSync
| TWindmillSync;
export type TListSecretSyncs = { secretSyncs: TSecretSync[] };
@@ -0,0 +1,16 @@
import { AppConnection } from "@app/hooks/api/appConnections/enums";
import { SecretSync } from "@app/hooks/api/secretSyncs";
import { TRootSecretSync } from "@app/hooks/api/secretSyncs/types/root-sync";
export type TWindmillSync = TRootSecretSync & {
destination: SecretSync.Windmill;
destinationConfig: {
workspace: string;
path: string;
};
connection: {
app: AppConnection.Windmill;
name: string;
id: string;
};
};
+1
View File
@@ -1,6 +1,7 @@
export {
useAddUserToWsE2EE,
useAddUserToWsNonE2EE,
useRevokeMySessionById,
useSendEmailVerificationCode,
useVerifyEmailVerificationCode
} from "./mutation";
+13
View File
@@ -171,3 +171,16 @@ export const useResendOrgMemberInvitation = () => {
}
});
};
export const useRevokeMySessionById = () => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: async (sessionId: string) => {
const { data } = await apiRequest.delete(`/api/v2/users/me/sessions/${sessionId}`);
return data;
},
onSuccess() {
queryClient.invalidateQueries({ queryKey: userKeys.mySessions });
}
});
};
@@ -57,7 +57,7 @@ import { MenuIconButton } from "../MenuIconButton";
import { ServerAdminsPanel } from "../ServerAdminsPanel/ServerAdminsPanel";
const getPlan = (subscription: SubscriptionPlan) => {
if (subscription.dynamicSecret) return "Enterprise Plan";
if (subscription.groups) return "Enterprise Plan";
if (subscription.pitRecovery) return "Pro Plan";
return "Free Plan";
};
@@ -2,7 +2,7 @@ import { useOrganization, useSubscription } from "@app/context";
import { SubscriptionPlan } from "@app/hooks/api/types";
const getPlan = (subscription: SubscriptionPlan) => {
if (subscription.dynamicSecret) return "Enterprise Plan";
if (subscription.groups) return "Enterprise Plan";
if (subscription.pitRecovery) return "Pro Plan";
return "Free Plan";
};
@@ -37,6 +37,7 @@ export const ProjectLayout = () => {
const { t } = useTranslation();
const workspaceId = currentWorkspace?.id || "";
const projectSlug = currentWorkspace?.slug || "";
const { subscription } = useSubscription();
const isSecretManager = currentWorkspace?.type === ProjectType.SecretManager;
const isCertManager = currentWorkspace?.type === ProjectType.CertificateManager;
@@ -53,7 +54,6 @@ export const ProjectLayout = () => {
});
// we only show the secret rotations v1 tab if they have existing rotations
const { subscription } = useSubscription();
const { data: secretRotations } = useGetSecretRotations({
workspaceId,
options: {
@@ -98,18 +98,46 @@ export const ProjectLayout = () => {
</Link>
)}
{isCertManager && (
<Link
to={`/${ProjectType.CertificateManager}/$projectId/overview` as const}
params={{
projectId: currentWorkspace.id
}}
>
{({ isActive }) => (
<MenuItem isSelected={isActive} icon="lock-closed">
Overview
</MenuItem>
)}
</Link>
<>
<Link
to={`/${ProjectType.CertificateManager}/$projectId/overview` as const}
params={{
projectId: currentWorkspace.id
}}
>
{({ isActive }) => (
<MenuItem isSelected={isActive} icon="certificate">
Certificates
</MenuItem>
)}
</Link>
<Link
to={
`/${ProjectType.CertificateManager}/$projectId/certificate-authorities` as const
}
params={{
projectId: currentWorkspace.id
}}
>
{({ isActive }) => (
<MenuItem isSelected={isActive} icon="certificate-authority">
Certificate Authorities
</MenuItem>
)}
</Link>
<Link
to={`/${ProjectType.CertificateManager}/$projectId/alerting` as const}
params={{
projectId: currentWorkspace.id
}}
>
{({ isActive }) => (
<MenuItem isSelected={isActive} icon="notification-bell">
Alerting
</MenuItem>
)}
</Link>
</>
)}
{isCmek && (
<Link
+62
View File
@@ -11,3 +11,65 @@ export const formatReservedPaths = (secretPath: string) => {
export const camelCaseToSpaces = (input: string) => {
return input.replace(/([a-z])([A-Z])/g, "$1 $2");
};
export const formatSessionUserAgent = (userAgent: string) => {
const result = {
os: "Unknown",
browser: "Unknown",
device: "Desktop"
};
// Operating System detection
if (userAgent.includes("Windows")) {
result.os = "Windows";
} else if (
userAgent.includes("Mac OS") ||
userAgent.includes("Macintosh") ||
userAgent.includes("macOS")
) {
result.os = "macOS";
} else if (userAgent.includes("Linux") && !userAgent.includes("Android")) {
result.os = "Linux";
} else if (userAgent.includes("Android")) {
result.os = "Android";
result.device = "Mobile";
} else if (
userAgent.includes("iOS") ||
userAgent.includes("iPhone") ||
userAgent.includes("iPad")
) {
result.os = "iOS";
result.device = userAgent.includes("iPad") ? "Tablet" : "Mobile";
}
// Browser detection
if (userAgent.includes("Firefox/")) {
result.browser = "Firefox";
} else if (userAgent.includes("Edge/") || userAgent.includes("Edg/")) {
result.browser = "Edge";
} else if (userAgent.includes("Brave/") || userAgent.includes("Brave ")) {
result.browser = "Brave";
} else if (
userAgent.includes("Chrome/") &&
!userAgent.includes("Chromium/") &&
!userAgent.includes("Edg/")
) {
result.browser = "Chrome";
} else if (
userAgent.includes("Safari/") &&
!userAgent.includes("Chrome/") &&
!userAgent.includes("Chromium/")
) {
result.browser = "Safari";
} else if (userAgent.includes("Opera/") || userAgent.includes("OPR/")) {
result.browser = "Opera";
} else if (userAgent.includes("Trident/") || userAgent.includes("MSIE")) {
result.browser = "Internet Explorer";
}
if (userAgent.toLowerCase() === "cli") {
result.browser = "CLI";
}
return result;
};
@@ -0,0 +1,29 @@
import { Helmet } from "react-helmet";
import { useTranslation } from "react-i18next";
import { ProjectPermissionCan } from "@app/components/permissions";
import { PageHeader } from "@app/components/v2";
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/context";
import { PkiAlertsSection } from "./components";
export const AlertingPage = () => {
const { t } = useTranslation();
return (
<div className="container mx-auto flex h-full flex-col justify-between bg-bunker-800 text-white">
<Helmet>
<title>{t("common.head-title", { title: "Alerting" })}</title>
</Helmet>
<div className="mx-auto mb-6 w-full max-w-7xl">
<PageHeader title="Alerting" />
<ProjectPermissionCan
renderGuardBanner
I={ProjectPermissionActions.Read}
a={ProjectPermissionSub.PkiAlerts}
>
<PkiAlertsSection />
</ProjectPermissionCan>
</div>
</div>
);
};
@@ -0,0 +1,19 @@
import { createFileRoute } from "@tanstack/react-router";
import { AlertingPage } from "./AlertingPage";
export const Route = createFileRoute(
"/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/alerting"
)({
component: AlertingPage,
beforeLoad: ({ context }) => {
return {
breadcrumbs: [
...context.breadcrumbs,
{
label: "Alerting"
}
]
};
}
});
@@ -20,9 +20,9 @@ import { useDeleteCa, useGetCaById } from "@app/hooks/api";
import { ProjectType } from "@app/hooks/api/workspace/types";
import { usePopUp } from "@app/hooks/usePopUp";
import { CaInstallCertModal } from "../CertificatesPage/components/CaTab/components/CaInstallCertModal";
import { CaModal } from "../CertificatesPage/components/CaTab/components/CaModal";
import { CertificateTemplatesSection } from "../CertificatesPage/components/CertificatesTab/components/CertificateTemplatesSection";
import { CaInstallCertModal } from "../CertificateAuthoritiesPage/components/CaInstallCertModal";
import { CaModal } from "../CertificateAuthoritiesPage/components/CaModal";
import { CertificateTemplatesSection } from "../CertificatesPage/components/CertificateTemplatesSection";
import {
CaCertificatesSection,
CaCrlsSection,
@@ -13,7 +13,7 @@ export const Route = createFileRoute(
{
label: "Certificate Authorities",
link: linkOptions({
to: "/cert-manager/$projectId/overview",
to: "/cert-manager/$projectId/certificate-authorities",
params: {
projectId: params.projectId
}
@@ -0,0 +1,29 @@
import { Helmet } from "react-helmet";
import { useTranslation } from "react-i18next";
import { ProjectPermissionCan } from "@app/components/permissions";
import { PageHeader } from "@app/components/v2";
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/context";
import { CaSection } from "./components";
export const CertificateAuthoritiesPage = () => {
const { t } = useTranslation();
return (
<div className="container mx-auto flex h-full flex-col justify-between bg-bunker-800 text-white">
<Helmet>
<title>{t("common.head-title", { title: "Certificate Authorities" })}</title>
</Helmet>
<div className="mx-auto mb-6 w-full max-w-7xl">
<PageHeader title="Certificate Authorities" />
<ProjectPermissionCan
renderGuardBanner
I={ProjectPermissionActions.Read}
a={ProjectPermissionSub.CertificateAuthorities}
>
<CaSection />
</ProjectPermissionCan>
</div>
</div>
);
};
@@ -2,7 +2,7 @@ import { Modal, ModalContent } from "@app/components/v2";
import { useGetCaCert } from "@app/hooks/api";
import { UsePopUpState } from "@app/hooks/usePopUp";
import { CertificateContent } from "../../CertificatesTab/components/CertificateContent";
import { CertificateContent } from "../../CertificatesPage/components/CertificateContent";
type Props = {
popUp: UsePopUpState<["caCert"]>;
@@ -0,0 +1,19 @@
import { createFileRoute } from "@tanstack/react-router";
import { CertificateAuthoritiesPage } from "./CertificateAuthoritiesPage";
export const Route = createFileRoute(
"/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/certificate-authorities"
)({
component: CertificateAuthoritiesPage,
beforeLoad: ({ context }) => {
return {
breadcrumbs: [
...context.breadcrumbs,
{
label: "Certificate Authorities"
}
]
};
}
});
@@ -2,63 +2,49 @@ import { Helmet } from "react-helmet";
import { useTranslation } from "react-i18next";
import { ProjectPermissionCan } from "@app/components/permissions";
import { PageHeader, Tab, TabList, TabPanel, Tabs } from "@app/components/v2";
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/context";
import { PageHeader } from "@app/components/v2";
import { ProjectPermissionActions, ProjectPermissionSub, useProjectPermission } from "@app/context";
import { CaTab, CertificatesTab, PkiAlertsTab } from "./components";
enum TabSections {
Ca = "certificate-authorities",
Certificates = "certificates",
Alerting = "alerting"
}
import { PkiCollectionSection } from "../AlertingPage/components";
import { CertificatesSection } from "./components";
export const CertificatesPage = () => {
const { t } = useTranslation();
const { permission } = useProjectPermission();
const canAccessPkiColl = permission.can(
ProjectPermissionActions.Read,
ProjectPermissionSub.PkiCollections
);
const canAccessCerts = permission.can(
ProjectPermissionActions.Read,
ProjectPermissionSub.Certificates
);
return (
<div className="container mx-auto flex h-full flex-col justify-between bg-bunker-800 text-white">
<Helmet>
<title>{t("common.head-title", { title: "Certificates" })}</title>
</Helmet>
<div className="mx-auto mb-6 w-full max-w-7xl">
<PageHeader title="Overview" />
<Tabs defaultValue={TabSections.Certificates}>
<TabList>
<Tab value={TabSections.Certificates}>Certificates</Tab>
<Tab value={TabSections.Ca}>Certificate Authorities</Tab>
<Tab value={TabSections.Alerting}>Alerting</Tab>
</TabList>
<TabPanel value={TabSections.Certificates}>
<ProjectPermissionCan
renderGuardBanner
passThrough={false}
I={ProjectPermissionActions.Read}
a={ProjectPermissionSub.Certificates}
>
<CertificatesTab />
</ProjectPermissionCan>
</TabPanel>
<TabPanel value={TabSections.Ca}>
<ProjectPermissionCan
renderGuardBanner
passThrough={false}
I={ProjectPermissionActions.Read}
a={ProjectPermissionSub.CertificateAuthorities}
>
<CaTab />
</ProjectPermissionCan>
</TabPanel>
<TabPanel value={TabSections.Alerting}>
<ProjectPermissionCan
renderGuardBanner
passThrough={false}
I={ProjectPermissionActions.Read}
a={ProjectPermissionSub.PkiAlerts}
>
<PkiAlertsTab />
</ProjectPermissionCan>
</TabPanel>
</Tabs>
<PageHeader title="Certificates" />
{/* If both are false, the section does not render. This is to prevent duplicate banners. */}
{(canAccessCerts || canAccessPkiColl) && (
<ProjectPermissionCan
renderGuardBanner
I={ProjectPermissionActions.Read}
a={ProjectPermissionSub.PkiCollections}
>
<PkiCollectionSection />
</ProjectPermissionCan>
)}
<ProjectPermissionCan
renderGuardBanner
I={ProjectPermissionActions.Read}
a={ProjectPermissionSub.Certificates}
>
<CertificatesSection />
</ProjectPermissionCan>
</div>
</div>
);
@@ -1,17 +0,0 @@
import { motion } from "framer-motion";
import { CaSection } from "./components";
export const CaTab = () => {
return (
<motion.div
key="panel-certificate-authorities"
transition={{ duration: 0.15 }}
initial={{ opacity: 0, translateX: 30 }}
animate={{ opacity: 1, translateX: 0 }}
exit={{ opacity: 0, translateX: 30 }}
>
<CaSection />
</motion.div>
);
};
@@ -1 +0,0 @@
export { CaTab } from "./CaTab";
@@ -1,21 +0,0 @@
import { motion } from "framer-motion";
import { PkiCollectionSection } from "../PkiAlertsTab/components";
// import { CertificateTemplatesSection } from "./components/CertificateTemplatesSection";
import { CertificatesSection } from "./components";
export const CertificatesTab = () => {
return (
<motion.div
key="panel-certificates"
transition={{ duration: 0.15 }}
initial={{ opacity: 0, translateX: 30 }}
animate={{ opacity: 1, translateX: 0 }}
exit={{ opacity: 0, translateX: 30 }}
>
<PkiCollectionSection />
{/* <CertificateTemplatesSection /> */}
<CertificatesSection />
</motion.div>
);
};
@@ -1 +0,0 @@
export { CertificatesSection } from "./CertificatesSection";
@@ -1 +0,0 @@
export { CertificatesTab } from "./CertificatesTab";
@@ -1,17 +0,0 @@
import { motion } from "framer-motion";
import { PkiAlertsSection } from "./components";
export const PkiAlertsTab = () => {
return (
<motion.div
key="panel-alerts"
transition={{ duration: 0.15 }}
initial={{ opacity: 0, translateX: 30 }}
animate={{ opacity: 1, translateX: 0 }}
exit={{ opacity: 0, translateX: 30 }}
>
<PkiAlertsSection />
</motion.div>
);
};
@@ -1 +0,0 @@
export { PkiAlertsTab } from "./PkiAlertsTab";
@@ -1,3 +1 @@
export { CaTab } from "./CaTab";
export { CertificatesTab } from "./CertificatesTab";
export { PkiAlertsTab } from "./PkiAlertsTab";
export { CertificatesSection } from "./CertificatesSection";
@@ -22,7 +22,7 @@ import { PkiItemType } from "@app/hooks/api/pkiCollections/constants";
import { ProjectType } from "@app/hooks/api/workspace/types";
import { usePopUp } from "@app/hooks/usePopUp";
import { PkiCollectionModal } from "../CertificatesPage/components/PkiAlertsTab/components/PkiCollectionModal";
import { PkiCollectionModal } from "../AlertingPage/components/PkiCollectionModal";
import { PkiCollectionDetailsSection, PkiCollectionItemsSection } from "./components";
export const PkiCollectionPage = () => {
@@ -1,6 +1,6 @@
import { Modal, ModalContent } from "@app/components/v2";
import { KmipClientCertificate } from "@app/hooks/api/kmip/types";
import { CertificateContent } from "@app/pages/cert-manager/CertificatesPage/components/CertificatesTab/components/CertificateContent";
import { CertificateContent } from "@app/pages/cert-manager/CertificatesPage/components/CertificateContent";
type Props = {
isOpen: boolean;
@@ -22,6 +22,7 @@ import { MsSqlConnectionForm } from "./MsSqlConnectionForm";
import { PostgresConnectionForm } from "./PostgresConnectionForm";
import { TerraformCloudConnectionForm } from "./TerraformCloudConnectionForm";
import { VercelConnectionForm } from "./VercelConnectionForm";
import { WindmillConnectionForm } from "./WindmillConnectionForm";
type FormProps = {
onComplete: (appConnection: TAppConnection) => void;
@@ -84,6 +85,8 @@ const CreateForm = ({ app, onComplete }: CreateFormProps) => {
return <MsSqlConnectionForm onSubmit={onSubmit} />;
case AppConnection.Camunda:
return <CamundaConnectionForm onSubmit={onSubmit} />;
case AppConnection.Windmill:
return <WindmillConnectionForm onSubmit={onSubmit} />;
case AppConnection.Auth0:
return <Auth0ConnectionForm onSubmit={onSubmit} />;
default:
@@ -146,6 +149,8 @@ const UpdateForm = ({ appConnection, onComplete }: UpdateFormProps) => {
return <MsSqlConnectionForm onSubmit={onSubmit} appConnection={appConnection} />;
case AppConnection.Camunda:
return <CamundaConnectionForm onSubmit={onSubmit} appConnection={appConnection} />;
case AppConnection.Windmill:
return <WindmillConnectionForm onSubmit={onSubmit} appConnection={appConnection} />;
case AppConnection.Auth0:
return <Auth0ConnectionForm onSubmit={onSubmit} appConnection={appConnection} />;
default:
@@ -0,0 +1,158 @@
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 { TWindmillConnection, WindmillConnectionMethod } from "@app/hooks/api/appConnections";
import { AppConnection } from "@app/hooks/api/appConnections/enums";
import {
genericAppConnectionFieldsSchema,
GenericAppConnectionsFields
} from "./GenericAppConnectionFields";
type Props = {
appConnection?: TWindmillConnection;
onSubmit: (formData: FormData) => void;
};
const rootSchema = genericAppConnectionFieldsSchema.extend({
app: z.literal(AppConnection.Windmill)
});
const formSchema = z.discriminatedUnion("method", [
rootSchema.extend({
method: z.literal(WindmillConnectionMethod.AccessToken),
credentials: z.object({
accessToken: z.string().trim().min(1, "Access Token required"),
instanceUrl: z
.string()
.trim()
.transform((value) => value || undefined)
.refine((value) => (!value ? true : z.string().url().safeParse(value).success), {
message: "Invalid instance URL"
})
.optional()
})
})
]);
type FormData = z.infer<typeof formSchema>;
export const WindmillConnectionForm = ({ appConnection, onSubmit }: Props) => {
const isUpdate = Boolean(appConnection);
const form = useForm<FormData>({
resolver: zodResolver(formSchema),
defaultValues: appConnection ?? {
app: AppConnection.Windmill,
method: WindmillConnectionMethod.AccessToken
}
});
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.Windmill].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(WindmillConnectionMethod).map((method) => {
return (
<SelectItem value={method} key={method}>
{getAppConnectionMethodDetails(method).name}{" "}
</SelectItem>
);
})}
</Select>
</FormControl>
)}
/>
<Controller
name="credentials.instanceUrl"
control={control}
shouldUnregister
render={({ field, fieldState: { error } }) => (
<FormControl
errorText={error?.message}
isError={Boolean(error?.message)}
label="Instance URL"
isOptional
tooltipClassName="max-w-sm"
tooltipText="Will default to Windmill Cloud if not specified."
>
<Input {...field} placeholder="https://app.windmill.dev" />
</FormControl>
)}
/>
<Controller
name="credentials.accessToken"
control={control}
shouldUnregister
render={({ field: { value, onChange }, fieldState: { error } }) => (
<FormControl
errorText={error?.message}
isError={Boolean(error?.message)}
label="Access Token"
>
<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 Windmill"}
</Button>
<ModalClose asChild>
<Button colorSchema="secondary" variant="plain">
Cancel
</Button>
</ModalClose>
</div>
</form>
</FormProvider>
);
};
@@ -45,6 +45,7 @@ export const OrgAuthTab = withPermission(
const { data: samlConfig, isPending: isLoadingSamlConfig } = useGetSSOConfig(
currentOrg?.id ?? ""
);
const { data: ldapConfig, isPending: isLoadingLdapConfig } = useGetLDAPConfig(
currentOrg?.id ?? ""
);
@@ -54,7 +55,8 @@ export const OrgAuthTab = withPermission(
!enabledLoginMethods || enabledLoginMethods.includes(method);
const isOidcConfigured = oidcConfig && (oidcConfig.discoveryURL || oidcConfig.issuer);
const isSamlConfigured = samlConfig && samlConfig.entryPoint;
const isSamlConfigured =
samlConfig && (samlConfig.entryPoint || samlConfig.issuer || samlConfig.cert);
const isLdapConfigured = ldapConfig && ldapConfig.url;
const shouldShowCreateIdentityProviderView =
@@ -301,6 +301,7 @@ export const SSOModal = ({ popUp, handlePopUpClose, handlePopUpToggle, hideDelet
label={renderLabels(authProvider).entryPoint}
errorText={error?.message}
isError={Boolean(error)}
isRequired
>
<Input
{...field}
@@ -0,0 +1,152 @@
import { faAws, faGoogle } from "@fortawesome/free-brands-svg-icons";
import { faCheck, faCopy, faEllipsis } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { twMerge } from "tailwind-merge";
import { createNotification } from "@app/components/notifications";
import { OrgPermissionCan } from "@app/components/permissions/OrgPermissionCan";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger
} from "@app/components/v2/Dropdown";
import { IconButton } from "@app/components/v2/IconButton";
import { Td, Tr } from "@app/components/v2/Table";
import { OrgPermissionActions, OrgPermissionSubjects } from "@app/context/OrgPermissionContext";
import { useToggle } from "@app/hooks";
import { ExternalKmsProvider, KmsListEntry } from "@app/hooks/api/kms/types";
import { SubscriptionPlan } from "@app/hooks/api/types";
import { UsePopUpState } from "@app/hooks/usePopUp";
type Props = {
kms: KmsListEntry;
handlePopUpOpen: (
popUpName: keyof UsePopUpState<["editExternalKms", "removeExternalKms", "upgradePlan"]>,
data?: {
kmsId?: string;
name?: string;
provider?: string;
}
) => void;
subscription: SubscriptionPlan;
};
export const ExternalKmsItem = ({ kms, handlePopUpOpen, subscription }: Props) => {
const [isKmsIdCopied, { timedToggle: toggleKmsIdCopied }] = useToggle(false);
const [isKmsAliasCopied, { timedToggle: toggleKmsAliasCopied }] = useToggle(false);
return (
<Tr key={kms.id}>
<Td className="flex max-w-xs items-center overflow-hidden text-ellipsis hover:overflow-auto hover:break-all">
{kms.externalKms.provider === ExternalKmsProvider.Aws && <FontAwesomeIcon icon={faAws} />}
{kms.externalKms.provider === ExternalKmsProvider.Gcp && (
<FontAwesomeIcon icon={faGoogle} />
)}
<div className="ml-2">{kms.externalKms.provider.toUpperCase()}</div>
</Td>
<Td>
<div className="group flex items-center gap-2">
{kms.name}
<IconButton
size="xs"
ariaLabel="copy icon"
colorSchema="secondary"
className="relative rounded-md opacity-0 group-hover:opacity-100"
onClick={() => {
if (isKmsAliasCopied) {
return;
}
navigator.clipboard.writeText(kms.name);
createNotification({
text: "KMS alias copied to clipboard",
type: "success"
});
toggleKmsAliasCopied(2000);
}}
>
<FontAwesomeIcon icon={isKmsAliasCopied ? faCheck : faCopy} />
</IconButton>
</div>
</Td>
<Td>
<div className="group flex items-center gap-2">
{kms.id}
<IconButton
size="xs"
ariaLabel="copy icon"
colorSchema="secondary"
className="relative rounded-md opacity-0 group-hover:opacity-100"
onClick={() => {
if (isKmsIdCopied) {
return;
}
navigator.clipboard.writeText(kms.id);
createNotification({
text: "KMS ID copied to clipboard",
type: "success"
});
toggleKmsIdCopied(2000);
}}
>
<FontAwesomeIcon icon={isKmsIdCopied ? faCheck : faCopy} />
</IconButton>
</div>
</Td>
<Td>
<DropdownMenu>
<DropdownMenuTrigger asChild className="rounded-lg">
<div className="flex justify-end hover:text-primary-400 data-[state=open]:text-primary-400">
<FontAwesomeIcon size="sm" icon={faEllipsis} />
</div>
</DropdownMenuTrigger>
<DropdownMenuContent align="start" className="p-1">
<OrgPermissionCan I={OrgPermissionActions.Edit} an={OrgPermissionSubjects.Kms}>
{(isAllowed) => (
<DropdownMenuItem
disabled={!isAllowed}
className={twMerge(
!isAllowed && "pointer-events-none cursor-not-allowed opacity-50"
)}
onClick={(e) => {
e.stopPropagation();
if (subscription && !subscription?.externalKms) {
handlePopUpOpen("upgradePlan");
return;
}
handlePopUpOpen("editExternalKms", {
kmsId: kms.id
});
}}
>
Edit
</DropdownMenuItem>
)}
</OrgPermissionCan>
<OrgPermissionCan I={OrgPermissionActions.Delete} an={OrgPermissionSubjects.Kms}>
{(isAllowed) => (
<DropdownMenuItem
disabled={!isAllowed}
className={twMerge(
!isAllowed && "pointer-events-none cursor-not-allowed opacity-50"
)}
onClick={(e) => {
e.stopPropagation();
handlePopUpOpen("removeExternalKms", {
name: kms.name,
kmsId: kms.id,
provider: kms.externalKms.provider
});
}}
>
Delete
</DropdownMenuItem>
)}
</OrgPermissionCan>
</DropdownMenuContent>
</DropdownMenu>
</Td>
</Tr>
);
};
@@ -1,7 +1,5 @@
import { faAws, faGoogle } from "@fortawesome/free-brands-svg-icons";
import { faEllipsis, faLock, faPlus } from "@fortawesome/free-solid-svg-icons";
import { faLock, faPlus } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { twMerge } from "tailwind-merge";
import { UpgradePlanModal } from "@app/components/license/UpgradePlanModal";
import { createNotification } from "@app/components/notifications";
@@ -9,10 +7,6 @@ import { OrgPermissionCan } from "@app/components/permissions";
import {
Button,
DeleteActionModal,
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
EmptyState,
Table,
TableContainer,
@@ -31,9 +25,9 @@ import {
import { withPermission } from "@app/hoc";
import { usePopUp } from "@app/hooks";
import { useGetExternalKmsList, useRemoveExternalKms } from "@app/hooks/api";
import { ExternalKmsProvider } from "@app/hooks/api/kms/types";
import { AddExternalKmsForm } from "./AddExternalKmsForm";
import { ExternalKmsItem } from "./ExternalKmsItem";
import { UpdateExternalKmsForm } from "./UpdateExternalKmsForm";
export const OrgEncryptionTab = withPermission(
@@ -102,6 +96,7 @@ export const OrgEncryptionTab = withPermission(
<Tr>
<Td>Provider</Td>
<Td>Alias</Td>
<Td>ID</Td>
</Tr>
</THead>
<TBody>
@@ -115,78 +110,12 @@ export const OrgEncryptionTab = withPermission(
)}
{!isExternalKmsListLoading &&
externalKmsList?.map((kms) => (
<Tr key={kms.id}>
<Td className="flex max-w-xs items-center overflow-hidden text-ellipsis hover:overflow-auto hover:break-all">
{kms.externalKms.provider === ExternalKmsProvider.Aws && (
<FontAwesomeIcon icon={faAws} />
)}
{kms.externalKms.provider === ExternalKmsProvider.Gcp && (
<FontAwesomeIcon icon={faGoogle} />
)}
<div className="ml-2">{kms.externalKms.provider.toUpperCase()}</div>
</Td>
<Td>{kms.name}</Td>
<Td>
<DropdownMenu>
<DropdownMenuTrigger asChild className="rounded-lg">
<div className="flex justify-end hover:text-primary-400 data-[state=open]:text-primary-400">
<FontAwesomeIcon size="sm" icon={faEllipsis} />
</div>
</DropdownMenuTrigger>
<DropdownMenuContent align="start" className="p-1">
<OrgPermissionCan
I={OrgPermissionActions.Edit}
an={OrgPermissionSubjects.Kms}
>
{(isAllowed) => (
<DropdownMenuItem
disabled={!isAllowed}
className={twMerge(
!isAllowed && "pointer-events-none cursor-not-allowed opacity-50"
)}
onClick={(e) => {
e.stopPropagation();
if (subscription && !subscription?.externalKms) {
handlePopUpOpen("upgradePlan");
return;
}
handlePopUpOpen("editExternalKms", {
kmsId: kms.id
});
}}
>
Edit
</DropdownMenuItem>
)}
</OrgPermissionCan>
<OrgPermissionCan
I={OrgPermissionActions.Delete}
an={OrgPermissionSubjects.Kms}
>
{(isAllowed) => (
<DropdownMenuItem
disabled={!isAllowed}
className={twMerge(
!isAllowed && "pointer-events-none cursor-not-allowed opacity-50"
)}
onClick={(e) => {
e.stopPropagation();
handlePopUpOpen("removeExternalKms", {
name: kms.name,
kmsId: kms.id,
provider: kms.externalKms.provider
});
}}
>
Delete
</DropdownMenuItem>
)}
</OrgPermissionCan>
</DropdownMenuContent>
</DropdownMenu>
</Td>
</Tr>
<ExternalKmsItem
key={kms.id}
kms={kms}
handlePopUpOpen={handlePopUpOpen}
subscription={subscription}
/>
))}
</TBody>
</Table>
@@ -11,6 +11,7 @@ import { GitHubSyncDestinationCol } from "./GitHubSyncDestinationCol";
import { HumanitecSyncDestinationCol } from "./HumanitecSyncDestinationCol";
import { TerraformCloudSyncDestinationCol } from "./TerraformCloudSyncDestinationCol";
import { VercelSyncDestinationCol } from "./VercelSyncDestinationCol";
import { WindmillSyncDestinationCol } from "./WindmillSyncDestinationCol";
type Props = {
secretSync: TSecretSync;
@@ -40,6 +41,8 @@ export const SecretSyncDestinationCol = ({ secretSync }: Props) => {
return <CamundaSyncDestinationCol secretSync={secretSync} />;
case SecretSync.Vercel:
return <VercelSyncDestinationCol secretSync={secretSync} />;
case SecretSync.Windmill:
return <WindmillSyncDestinationCol secretSync={secretSync} />;
default:
throw new Error(
`Unhandled Secret Sync Destination Col: ${(secretSync as TSecretSync).destination}`
@@ -0,0 +1,14 @@
import { TWindmillSync } from "@app/hooks/api/secretSyncs/types/windmill-sync";
import { getSecretSyncDestinationColValues } from "../helpers";
import { SecretSyncTableCell } from "../SecretSyncTableCell";
type Props = {
secretSync: TWindmillSync;
};
export const WindmillSyncDestinationCol = ({ secretSync }: Props) => {
const { primaryText, secondaryText } = getSecretSyncDestinationColValues(secretSync);
return <SecretSyncTableCell primaryText={primaryText} secondaryText={secondaryText} />;
};
@@ -90,6 +90,10 @@ export const getSecretSyncDestinationColValues = (secretSync: TSecretSync) => {
primaryText = destinationConfig.appName || destinationConfig.app;
secondaryText = destinationConfig.env;
break;
case SecretSync.Windmill:
primaryText = destinationConfig.workspace;
secondaryText = destinationConfig.path;
break;
default:
throw new Error(`Unhandled Destination Col Values ${destination}`);
}
@@ -252,8 +252,12 @@ export const SecretDetailSidebar = ({
switch (userType) {
case ActorType.PLATFORM:
return "System-generated";
case ActorType.IDENTITY:
return userName || "Deleted Identity";
case ActorType.USER:
return userName || "Deleted User";
default:
return userName;
return "Unknown";
}
};
@@ -9,18 +9,19 @@ import { ProjectPermissionSub } from "@app/context";
import { ProjectPermissionSecretSyncActions } from "@app/context/ProjectPermissionContext/types";
import { APP_CONNECTION_MAP } from "@app/helpers/appConnections";
import { SecretSync, TSecretSync } from "@app/hooks/api/secretSyncs";
import { AwsParameterStoreSyncDestinationSection } from "@app/pages/secret-manager/SecretSyncDetailsByIDPage/components/SecretSyncDestinationSection/AwsParameterStoreSyncDestinationSection";
import { AwsSecretsManagerSyncDestinationSection } from "@app/pages/secret-manager/SecretSyncDetailsByIDPage/components/SecretSyncDestinationSection/AwsSecretsManagerSyncDestinationSection";
import { DatabricksSyncDestinationSection } from "@app/pages/secret-manager/SecretSyncDetailsByIDPage/components/SecretSyncDestinationSection/DatabricksSyncDestinationSection";
import { GitHubSyncDestinationSection } from "@app/pages/secret-manager/SecretSyncDetailsByIDPage/components/SecretSyncDestinationSection/GitHubSyncDestinationSection";
import { AwsParameterStoreSyncDestinationSection } from "./AwsParameterStoreSyncDestinationSection";
import { AwsSecretsManagerSyncDestinationSection } from "./AwsSecretsManagerSyncDestinationSection";
import { AzureAppConfigurationSyncDestinationSection } from "./AzureAppConfigurationSyncDestinationSection";
import { AzureKeyVaultSyncDestinationSection } from "./AzureKeyVaultSyncDestinationSection";
import { CamundaSyncDestinationSection } from "./CamundaSyncDestinationSection";
import { DatabricksSyncDestinationSection } from "./DatabricksSyncDestinationSection";
import { GcpSyncDestinationSection } from "./GcpSyncDestinationSection";
import { GitHubSyncDestinationSection } from "./GitHubSyncDestinationSection";
import { HumanitecSyncDestinationSection } from "./HumanitecSyncDestinationSection";
import { TerraformCloudSyncDestinationSection } from "./TerraformCloudSyncDestinationSection";
import { VercelSyncDestinationSection } from "./VercelSyncDestinationSection";
import { WindmillSyncDestinationSection } from "./WindmillSyncDestinationSection";
type Props = {
secretSync: TSecretSync;
@@ -69,6 +70,9 @@ export const SecretSyncDestinationSection = ({ secretSync, onEditDestination }:
case SecretSync.Vercel:
DestinationComponents = <VercelSyncDestinationSection secretSync={secretSync} />;
break;
case SecretSync.Windmill:
DestinationComponents = <WindmillSyncDestinationSection secretSync={secretSync} />;
break;
default:
throw new Error(`Unhandled Destination Section components: ${destination}`);
}
@@ -0,0 +1,19 @@
import { GenericFieldLabel } from "@app/components/secret-syncs";
import { TWindmillSync } from "@app/hooks/api/secretSyncs/types/windmill-sync";
type Props = {
secretSync: TWindmillSync;
};
export const WindmillSyncDestinationSection = ({ secretSync }: Props) => {
const {
destinationConfig: { path, workspace }
} = secretSync;
return (
<>
<GenericFieldLabel label="Workspace">{workspace}</GenericFieldLabel>
<GenericFieldLabel label="Path">{path}</GenericFieldLabel>
</>
);
};
@@ -51,6 +51,7 @@ export const SecretSyncOptionsSection = ({ secretSync, onEditOptions }: Props) =
case SecretSync.TerraformCloud:
case SecretSync.Camunda:
case SecretSync.Vercel:
case SecretSync.Windmill:
AdditionalSyncOptionsComponent = null;
break;
default:
@@ -3,7 +3,7 @@ import { useNavigate } from "@tanstack/react-router";
import { createNotification } from "@app/components/notifications";
import { ProjectPermissionCan } from "@app/components/permissions";
import { Button, DeleteActionModal } from "@app/components/v2";
import { Button, DeleteActionModal, Tooltip } from "@app/components/v2";
import { LeaveProjectModal } from "@app/components/v2/LeaveProjectModal";
import {
ProjectPermissionActions,
@@ -142,16 +142,22 @@ export const DeleteProjectSection = () => {
<div className="space-x-4">
<ProjectPermissionCan I={ProjectPermissionActions.Delete} a={ProjectPermissionSub.Project}>
{(isAllowed) => (
<Button
isLoading={isDeleting}
isDisabled={!isAllowed || isDeleting || currentWorkspace?.hasDeleteProtection}
colorSchema="danger"
variant="outline_bg"
type="submit"
onClick={() => handlePopUpOpen("deleteWorkspace")}
<Tooltip
className="max-w-sm"
content="This project is protected from deletion. To delete it, disable delete protection first."
isDisabled={!currentWorkspace?.hasDeleteProtection}
>
{`Delete ${currentWorkspace?.name}`}
</Button>
<Button
isLoading={isDeleting}
isDisabled={!isAllowed || isDeleting || currentWorkspace?.hasDeleteProtection}
colorSchema="danger"
variant="outline_bg"
type="submit"
onClick={() => handlePopUpOpen("deleteWorkspace")}
>
{`Delete ${currentWorkspace?.name}`}
</Button>
</Tooltip>
)}
</ProjectPermissionCan>
{!isOnlyAdminMember && (
@@ -1,6 +1,8 @@
import { faServer } from "@fortawesome/free-solid-svg-icons";
import { createNotification } from "@app/components/notifications";
import {
DeleteActionModal,
EmptyState,
Table,
TableContainer,
@@ -9,59 +11,131 @@ import {
Td,
Th,
THead,
Tooltip,
Tr
} from "@app/components/v2";
import { useGetMySessions } from "@app/hooks/api";
import { Button } from "@app/components/v2/Button";
import { useGetMySessions, useRevokeMySessionById } from "@app/hooks/api";
import { usePopUp } from "@app/hooks/usePopUp";
import { timeAgo } from "@app/lib/fn/date";
import { formatSessionUserAgent } from "@app/lib/fn/string";
const formatLocalDateTime = (date: Date): string => {
return date.toLocaleString(undefined, {
weekday: "long",
year: "numeric",
month: "long",
day: "numeric",
hour: "2-digit",
minute: "2-digit",
second: "2-digit"
});
};
export const SessionsTable = () => {
const { data, isPending } = useGetMySessions();
const { mutateAsync: revokeMySessionById } = useRevokeMySessionById();
const { popUp, handlePopUpOpen, handlePopUpClose, handlePopUpToggle } = usePopUp([
"deleteSession"
] as const);
const formatDate = (dateToFormat: string) => {
const date = new Date(dateToFormat);
const year = date.getFullYear();
const month = date.getMonth() + 1;
const day = date.getDate();
const handleSignOut = async (sessionId: string) => {
try {
await revokeMySessionById(sessionId);
createNotification({
text: "Session revoked successfully",
type: "success"
});
} catch (error) {
console.error(error);
createNotification({
text: "Failed to revoke session",
type: "error"
});
}
const formattedDate = `${day}/${month}/${year}`;
return formattedDate;
handlePopUpClose("deleteSession");
};
return (
<TableContainer className="mt-4">
<Table>
<THead>
<Tr>
<Th>Created</Th>
<Th>Last active</Th>
<Th>IP address</Th>
<Th>Device</Th>
</Tr>
</THead>
<TBody>
{isPending && <TableSkeleton columns={4} innerKey="sesssions" />}
{!isPending &&
data &&
data.length > 0 &&
data.map(({ id, createdAt, lastUsed, ip, userAgent }) => {
return (
<Tr className="h-10" key={`session-${id}`}>
<Td>{formatDate(createdAt)}</Td>
<Td>{formatDate(lastUsed)}</Td>
<Td>{ip}</Td>
<Td>{userAgent}</Td>
</Tr>
);
})}
{!isPending && data && data?.length === 0 && (
<>
<DeleteActionModal
isOpen={popUp.deleteSession.isOpen}
title="Are you sure want to sign out of this session?"
onChange={(isOpen) => handlePopUpToggle("deleteSession", isOpen)}
deleteKey="confirm"
onDeleteApproved={() =>
handleSignOut((popUp?.deleteSession?.data as { sessionId: string })?.sessionId)
}
/>
<TableContainer className="mt-4">
<Table>
<THead>
<Tr>
<Td colSpan={4}>
<EmptyState title="No sessions on file" icon={faServer} />
</Td>
<Th>IP & Session ID</Th>
<Th>OS & Browser</Th>
<Th>Last accessed</Th>
<Th>Manage</Th>
</Tr>
)}
</TBody>
</Table>
</TableContainer>
</THead>
<TBody>
{isPending && <TableSkeleton columns={4} innerKey="sessions" />}
{!isPending &&
data &&
data.length > 0 &&
data.map(({ id, createdAt, lastUsed, ip, userAgent }) => {
const { os, browser } = formatSessionUserAgent(userAgent);
const lastUsedDate = new Date(lastUsed);
const createdAtDate = new Date(createdAt);
return (
<Tr className="h-20" key={`session-${id}`}>
<Td>
<div className="flex flex-col">
<span className="font-medium">{ip}</span>
<span className="text-sm text-gray-500">ID: {id}</span>
</div>
</Td>
<Td>
<div className="flex flex-col">
<span className="font-medium">{os}</span>
<span className="text-sm text-gray-500">{browser}</span>
</div>
</Td>
<Td>
<div className="flex flex-col">
<Tooltip content={formatLocalDateTime(lastUsedDate)}>
<span className="font-medium">{timeAgo(lastUsedDate, new Date())}</span>
</Tooltip>
<Tooltip content={formatLocalDateTime(createdAtDate)}>
<span className="text-sm text-gray-500">
Created {timeAgo(createdAtDate, new Date())}
</span>
</Tooltip>
</div>
</Td>
<Td>
<Button
variant="plain"
colorSchema="danger"
onClick={() => handlePopUpOpen("deleteSession", { sessionId: id })}
>
Sign out
</Button>
</Td>
</Tr>
);
})}
{!isPending && data && data?.length === 0 && (
<Tr>
<Td colSpan={4}>
<EmptyState title="No sessions on file" icon={faServer} />
</Td>
</Tr>
)}
</TBody>
</Table>
</TableContainer>
</>
);
};
+57
View File
@@ -92,6 +92,8 @@ import { Route as kmsOverviewPageRouteImport } from './pages/kms/OverviewPage/ro
import { Route as kmsKmipPageRouteImport } from './pages/kms/KmipPage/route'
import { Route as certManagerSettingsPageRouteImport } from './pages/cert-manager/SettingsPage/route'
import { Route as certManagerCertificatesPageRouteImport } from './pages/cert-manager/CertificatesPage/route'
import { Route as certManagerCertificateAuthoritiesPageRouteImport } from './pages/cert-manager/CertificateAuthoritiesPage/route'
import { Route as certManagerAlertingPageRouteImport } from './pages/cert-manager/AlertingPage/route'
import { Route as projectRoleDetailsBySlugPageRouteSshImport } from './pages/project/RoleDetailsBySlugPage/route-ssh'
import { Route as projectMemberDetailsByIDPageRouteSshImport } from './pages/project/MemberDetailsByIDPage/route-ssh'
import { Route as projectIdentityDetailsByIDPageRouteSshImport } from './pages/project/IdentityDetailsByIDPage/route-ssh'
@@ -894,6 +896,20 @@ const certManagerCertificatesPageRouteRoute =
getParentRoute: () => certManagerLayoutRoute,
} as any)
const certManagerCertificateAuthoritiesPageRouteRoute =
certManagerCertificateAuthoritiesPageRouteImport.update({
id: '/certificate-authorities',
path: '/certificate-authorities',
getParentRoute: () => certManagerLayoutRoute,
} as any)
const certManagerAlertingPageRouteRoute =
certManagerAlertingPageRouteImport.update({
id: '/alerting',
path: '/alerting',
getParentRoute: () => certManagerLayoutRoute,
} as any)
const projectRoleDetailsBySlugPageRouteSshRoute =
projectRoleDetailsBySlugPageRouteSshImport.update({
id: '/roles/$roleSlug',
@@ -2104,6 +2120,20 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof sshLayoutImport
parentRoute: typeof AuthenticateInjectOrgDetailsOrgLayoutSshProjectIdImport
}
'/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/alerting': {
id: '/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/alerting'
path: '/alerting'
fullPath: '/cert-manager/$projectId/alerting'
preLoaderRoute: typeof certManagerAlertingPageRouteImport
parentRoute: typeof certManagerLayoutImport
}
'/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/certificate-authorities': {
id: '/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/certificate-authorities'
path: '/certificate-authorities'
fullPath: '/cert-manager/$projectId/certificate-authorities'
preLoaderRoute: typeof certManagerCertificateAuthoritiesPageRouteImport
parentRoute: typeof certManagerLayoutImport
}
'/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/overview': {
id: '/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/overview'
path: '/overview'
@@ -3119,6 +3149,8 @@ const AuthenticateInjectOrgDetailsOrgLayoutOrganizationRouteWithChildren =
)
interface certManagerLayoutRouteChildren {
certManagerAlertingPageRouteRoute: typeof certManagerAlertingPageRouteRoute
certManagerCertificateAuthoritiesPageRouteRoute: typeof certManagerCertificateAuthoritiesPageRouteRoute
certManagerCertificatesPageRouteRoute: typeof certManagerCertificatesPageRouteRoute
certManagerSettingsPageRouteRoute: typeof certManagerSettingsPageRouteRoute
projectAccessControlPageRouteCertManagerRoute: typeof projectAccessControlPageRouteCertManagerRoute
@@ -3130,6 +3162,9 @@ interface certManagerLayoutRouteChildren {
}
const certManagerLayoutRouteChildren: certManagerLayoutRouteChildren = {
certManagerAlertingPageRouteRoute: certManagerAlertingPageRouteRoute,
certManagerCertificateAuthoritiesPageRouteRoute:
certManagerCertificateAuthoritiesPageRouteRoute,
certManagerCertificatesPageRouteRoute: certManagerCertificatesPageRouteRoute,
certManagerSettingsPageRouteRoute: certManagerSettingsPageRouteRoute,
projectAccessControlPageRouteCertManagerRoute:
@@ -3794,6 +3829,8 @@ export interface FileRoutesByFullPath {
'/organization/secret-manager/overview': typeof organizationSecretManagerOverviewPageRouteRoute
'/organization/secret-sharing/settings': typeof organizationSecretSharingSettingsPageRouteRoute
'/organization/ssh/overview': typeof organizationSshOverviewPageRouteRoute
'/cert-manager/$projectId/alerting': typeof certManagerAlertingPageRouteRoute
'/cert-manager/$projectId/certificate-authorities': typeof certManagerCertificateAuthoritiesPageRouteRoute
'/cert-manager/$projectId/overview': typeof certManagerCertificatesPageRouteRoute
'/cert-manager/$projectId/settings': typeof certManagerSettingsPageRouteRoute
'/kms/$projectId/kmip': typeof kmsKmipPageRouteRoute
@@ -3969,6 +4006,8 @@ export interface FileRoutesByTo {
'/organization/secret-manager/overview': typeof organizationSecretManagerOverviewPageRouteRoute
'/organization/secret-sharing/settings': typeof organizationSecretSharingSettingsPageRouteRoute
'/organization/ssh/overview': typeof organizationSshOverviewPageRouteRoute
'/cert-manager/$projectId/alerting': typeof certManagerAlertingPageRouteRoute
'/cert-manager/$projectId/certificate-authorities': typeof certManagerCertificateAuthoritiesPageRouteRoute
'/cert-manager/$projectId/overview': typeof certManagerCertificatesPageRouteRoute
'/cert-manager/$projectId/settings': typeof certManagerSettingsPageRouteRoute
'/kms/$projectId/kmip': typeof kmsKmipPageRouteRoute
@@ -4160,6 +4199,8 @@ export interface FileRoutesById {
'/_authenticate/_inject-org-details/_org-layout/kms/$projectId/_kms-layout': typeof kmsLayoutRouteWithChildren
'/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId/_secret-manager-layout': typeof secretManagerLayoutRouteWithChildren
'/_authenticate/_inject-org-details/_org-layout/ssh/$projectId/_ssh-layout': typeof sshLayoutRouteWithChildren
'/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/alerting': typeof certManagerAlertingPageRouteRoute
'/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/certificate-authorities': typeof certManagerCertificateAuthoritiesPageRouteRoute
'/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/overview': typeof certManagerCertificatesPageRouteRoute
'/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/settings': typeof certManagerSettingsPageRouteRoute
'/_authenticate/_inject-org-details/_org-layout/kms/$projectId/_kms-layout/kmip': typeof kmsKmipPageRouteRoute
@@ -4344,6 +4385,8 @@ export interface FileRouteTypes {
| '/organization/secret-manager/overview'
| '/organization/secret-sharing/settings'
| '/organization/ssh/overview'
| '/cert-manager/$projectId/alerting'
| '/cert-manager/$projectId/certificate-authorities'
| '/cert-manager/$projectId/overview'
| '/cert-manager/$projectId/settings'
| '/kms/$projectId/kmip'
@@ -4518,6 +4561,8 @@ export interface FileRouteTypes {
| '/organization/secret-manager/overview'
| '/organization/secret-sharing/settings'
| '/organization/ssh/overview'
| '/cert-manager/$projectId/alerting'
| '/cert-manager/$projectId/certificate-authorities'
| '/cert-manager/$projectId/overview'
| '/cert-manager/$projectId/settings'
| '/kms/$projectId/kmip'
@@ -4707,6 +4752,8 @@ export interface FileRouteTypes {
| '/_authenticate/_inject-org-details/_org-layout/kms/$projectId/_kms-layout'
| '/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId/_secret-manager-layout'
| '/_authenticate/_inject-org-details/_org-layout/ssh/$projectId/_ssh-layout'
| '/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/alerting'
| '/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/certificate-authorities'
| '/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/overview'
| '/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/settings'
| '/_authenticate/_inject-org-details/_org-layout/kms/$projectId/_kms-layout/kmip'
@@ -5222,6 +5269,8 @@ export const routeTree = rootRoute
"filePath": "cert-manager/layout.tsx",
"parent": "/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId",
"children": [
"/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/alerting",
"/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/certificate-authorities",
"/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/overview",
"/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/settings",
"/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/access-management",
@@ -5277,6 +5326,14 @@ export const routeTree = rootRoute
"/_authenticate/_inject-org-details/_org-layout/ssh/$projectId/_ssh-layout/roles/$roleSlug"
]
},
"/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/alerting": {
"filePath": "cert-manager/AlertingPage/route.tsx",
"parent": "/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout"
},
"/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/certificate-authorities": {
"filePath": "cert-manager/CertificateAuthoritiesPage/route.tsx",
"parent": "/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout"
},
"/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/overview": {
"filePath": "cert-manager/CertificatesPage/route.tsx",
"parent": "/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout"
+2
View File
@@ -282,6 +282,8 @@ const secretManagerIntegrationsRedirect = route("/integrations", [
const certManagerRoutes = route("/cert-manager/$projectId", [
layout("cert-manager-layout", "cert-manager/layout.tsx", [
route("/overview", "cert-manager/CertificatesPage/route.tsx"),
route("/certificate-authorities", "cert-manager/CertificateAuthoritiesPage/route.tsx"),
route("/alerting", "cert-manager/AlertingPage/route.tsx"),
route("/ca/$caId", "cert-manager/CertAuthDetailsByIDPage/route.tsx"),
route("/pki-collections/$collectionId", "cert-manager/PkiCollectionDetailsByIDPage/routes.tsx"),
route("/settings", "cert-manager/SettingsPage/route.tsx"),