Merge branch 'main' into feat/multipleApprovalEnvs

This commit is contained in:
Carlos Monastyrski
2025-07-24 12:03:07 -03:00
595 changed files with 12408 additions and 6322 deletions
@@ -6,7 +6,6 @@ import { twMerge } from "tailwind-merge";
import { useOrganization, useWorkspace } from "@app/context";
import { useToggle } from "@app/hooks";
import { ProjectType } from "@app/hooks/api/workspace/types";
import { createNotification } from "../notifications";
import { IconButton, Select, SelectItem, Tooltip } from "../v2";
@@ -93,7 +92,7 @@ export default function NavHeader({
<FontAwesomeIcon icon={faAngleRight} className="ml-3 mr-3 text-sm text-gray-400" />
{pageName === "Secrets" ? (
<Link
to="/projects/$projectId/secret-manager/overview"
to="/projects/secret-management/$projectId/overview"
params={{ projectId: currentWorkspace.id }}
className="text-sm font-semibold text-primary/80 hover:text-primary"
>
@@ -129,7 +128,7 @@ export default function NavHeader({
<div className="flex items-center space-x-3">
<FontAwesomeIcon icon={faAngleRight} className="ml-3 mr-1.5 text-xs text-gray-400" />
<Link
to={`/projects/$projectId/${ProjectType.SecretManager}/secrets/$envSlug` as const}
to="/projects/secret-management/$projectId/secrets/$envSlug"
params={{ projectId: currentWorkspace.id, envSlug: routerEnvSlug }}
className="text-sm font-semibold text-primary/80 hover:text-primary"
>
@@ -191,7 +190,7 @@ export default function NavHeader({
</div>
) : (
<Link
to={`/projects/$projectId/${ProjectType.SecretManager}/secrets/$envSlug` as const}
to="/projects/secret-management/$projectId/secrets/$envSlug"
params={{
projectId: currentWorkspace.id,
envSlug: routerEnvSlug || ""
@@ -4,7 +4,6 @@ import { Link } from "@tanstack/react-router";
import { twMerge } from "tailwind-merge";
import { useTimedReset } from "@app/hooks";
import { ProjectType } from "@app/hooks/api/workspace/types";
import { createNotification } from "../notifications";
import { IconButton, Tooltip } from "../v2";
@@ -70,7 +69,7 @@ export const SecretDashboardPathBreadcrumb = ({
</div>
) : (
<Link
to={`/projects/$projectId/${ProjectType.SecretManager}/secrets/$envSlug` as const}
to="/projects/secret-management/$projectId/secrets/$envSlug"
params={{
projectId,
envSlug: environmentSlug
@@ -5,14 +5,12 @@ import { z } from "zod";
import { createNotification } from "@app/components/notifications";
import { ProjectPermissionCan } from "@app/components/permissions";
import { Button, FormControl, Input, Select, SelectItem, TextArea } from "@app/components/v2";
import { Button, FormControl, Input, TextArea } from "@app/components/v2";
import { ProjectPermissionActions, ProjectPermissionSub, useWorkspace } from "@app/context";
import { useUpdateProject } from "@app/hooks/api";
import { ProjectType } from "@app/hooks/api/workspace/types";
const baseFormSchema = z.object({
name: z.string().min(1, "Required").max(64, "Too long, maximum length is 64 characters"),
defaultProduct: z.nativeEnum(ProjectType).default(ProjectType.SecretManager),
description: z
.string()
.trim()
@@ -52,7 +50,6 @@ export const ProjectOverviewChangeSection = ({ showSlugField = false }: Props) =
reset({
name: currentWorkspace.name,
description: currentWorkspace.description ?? "",
defaultProduct: currentWorkspace.defaultProduct,
...(showSlugField && { slug: currentWorkspace.slug })
});
}
@@ -66,7 +63,6 @@ export const ProjectOverviewChangeSection = ({ showSlugField = false }: Props) =
projectID: currentWorkspace.id,
newProjectName: data.name,
newProjectDescription: data.description,
defaultProduct: data.defaultProduct,
...(showSlugField &&
"slug" in data && {
newSlug: data.slug !== currentWorkspace.slug ? data.slug : undefined
@@ -216,31 +212,6 @@ export const ProjectOverviewChangeSection = ({ showSlugField = false }: Props) =
</ProjectPermissionCan>
</div>
</div>
<Controller
control={control}
name="defaultProduct"
defaultValue={ProjectType.SecretManager}
render={({ field: { onChange, ...field }, fieldState: { error } }) => (
<FormControl
label="Default Product"
errorText={error?.message}
isError={Boolean(error)}
>
<Select
defaultValue={field.value}
{...field}
onValueChange={(e) => onChange(e)}
className="w-full max-w-md bg-mineshaft-800"
>
<SelectItem value={ProjectType.SecretManager}>Secrets Manager</SelectItem>
<SelectItem value={ProjectType.CertificateManager}>PKI Manager</SelectItem>
<SelectItem value={ProjectType.KMS}>KMS</SelectItem>
<SelectItem value={ProjectType.SSH}>SSH</SelectItem>
<SelectItem value={ProjectType.SecretScanning}>Secret Scanning</SelectItem>
</Select>
</FormControl>
)}
/>
<div>
<ProjectPermissionCan
I={ProjectPermissionActions.Edit}
@@ -4,6 +4,7 @@ import { faInfoCircle } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { zodResolver } from "@hookform/resolvers/zod";
import { useNavigate } from "@tanstack/react-router";
import { twMerge } from "tailwind-merge";
import z from "zod";
import { createNotification } from "@app/components/notifications";
@@ -16,6 +17,7 @@ import {
Button,
FormControl,
Input,
Lottie,
Modal,
ModalClose,
ModalContent,
@@ -31,10 +33,11 @@ import {
useSubscription,
useUser
} from "@app/context";
import { getProjectHomePage } from "@app/helpers/project";
import { getProjectHomePage, getProjectLottieIcon } from "@app/helpers/project";
import { useCreateWorkspace, useGetExternalKmsList, useGetUserWorkspaces } from "@app/hooks/api";
import { INTERNAL_KMS_KEY_ID } from "@app/hooks/api/kms/types";
import { InfisicalProjectTemplate, useListProjectTemplates } from "@app/hooks/api/projectTemplates";
import { ProjectType } from "@app/hooks/api/workspace/types";
const formSchema = z.object({
name: z.string().trim().min(1, "Required").max(64, "Too long, maximum length is 64 characters"),
@@ -43,6 +46,7 @@ const formSchema = z.object({
.trim()
.max(256, "Description too long, max length is 256 characters")
.optional(),
type: z.nativeEnum(ProjectType),
kmsKeyId: z.string(),
template: z.string()
});
@@ -56,6 +60,29 @@ interface NewProjectModalProps {
type NewProjectFormProps = Pick<NewProjectModalProps, "onOpenChange">;
const PROJECT_TYPE_MENU_ITEMS = [
{
label: "Secrets Management",
value: ProjectType.SecretManager
},
{
label: "Certificates Management",
value: ProjectType.CertificateManager
},
{
label: "KMS",
value: ProjectType.KMS
},
{
label: "SSH",
value: ProjectType.SSH
},
{
label: "Secret Scanning",
value: ProjectType.SecretScanning
}
];
const NewProjectForm = ({ onOpenChange }: NewProjectFormProps) => {
const navigate = useNavigate();
const { currentOrg } = useOrganization();
@@ -70,18 +97,11 @@ const NewProjectForm = ({ onOpenChange }: NewProjectFormProps) => {
OrgPermissionSubjects.ProjectTemplates
);
const { data: projectTemplates = [] } = useListProjectTemplates({
enabled: Boolean(canReadProjectTemplates && subscription?.projectTemplates)
});
const { data: externalKmsList } = useGetExternalKmsList(currentOrg.id, {
enabled: permission.can(OrgPermissionActions.Read, OrgPermissionSubjects.Kms)
});
const {
control,
handleSubmit,
reset,
watch,
formState: { isSubmitting, errors }
} = useForm<TAddProjectFormData>({
resolver: zodResolver(formSchema),
@@ -91,6 +111,16 @@ const NewProjectForm = ({ onOpenChange }: NewProjectFormProps) => {
}
});
const selectedProjectType = watch("type");
const { data: projectTemplates = [] } = useListProjectTemplates({
enabled: Boolean(canReadProjectTemplates && subscription?.projectTemplates),
select: (template) => template.filter((el) => el.type === selectedProjectType)
});
const { data: externalKmsList } = useGetExternalKmsList(currentOrg.id, {
enabled: permission.can(OrgPermissionActions.Read, OrgPermissionSubjects.Kms)
});
useEffect(() => {
if (Object.keys(errors).length > 0) {
console.log("Current form errors:", errors);
@@ -101,7 +131,8 @@ const NewProjectForm = ({ onOpenChange }: NewProjectFormProps) => {
name,
description,
kmsKeyId,
template
template,
type
}: TAddProjectFormData) => {
// type check
if (!currentOrg) return;
@@ -113,7 +144,8 @@ const NewProjectForm = ({ onOpenChange }: NewProjectFormProps) => {
projectName: name,
projectDescription: description,
kmsKeyId: kmsKeyId !== INTERNAL_KMS_KEY_ID ? kmsKeyId : undefined,
template
template,
type
});
await refetchWorkspaces();
@@ -121,7 +153,7 @@ const NewProjectForm = ({ onOpenChange }: NewProjectFormProps) => {
reset();
onOpenChange(false);
navigate({
to: getProjectHomePage(project.defaultProduct),
to: getProjectHomePage(project.type),
params: { projectId: project.id }
});
} catch (err) {
@@ -150,6 +182,42 @@ const NewProjectForm = ({ onOpenChange }: NewProjectFormProps) => {
</FormControl>
)}
/>
<Controller
control={control}
name="type"
defaultValue={ProjectType.SecretManager}
render={({ field, fieldState: { error } }) => (
<FormControl
label="Project Type"
isError={Boolean(error)}
errorText={error?.message}
className="flex-1"
>
<div className="mt-2 grid grid-cols-5 gap-4">
{PROJECT_TYPE_MENU_ITEMS.map((el) => (
<div
key={el.value}
className={twMerge(
"flex cursor-pointer flex-col items-center gap-2 rounded border border-mineshaft-600 p-4 opacity-75 transition-all hover:border-primary-400 hover:bg-mineshaft-600",
field.value === el.value && "border-primary-400 bg-mineshaft-600 opacity-100"
)}
onClick={() => field.onChange(el.value)}
role="button"
tabIndex={0}
onKeyDown={(e) => {
if (e.key === "Enter") {
field.onChange(el.value);
}
}}
>
<Lottie icon={getProjectLottieIcon(el.value)} className="h-8 w-8" />
<div className="text-center text-xs">{el.label}</div>
</div>
))}
</div>
</FormControl>
)}
/>
<Controller
control={control}
name="description"
@@ -171,6 +239,7 @@ const NewProjectForm = ({ onOpenChange }: NewProjectFormProps) => {
</FormControl>
)}
/>
<Controller
control={control}
name="template"
@@ -0,0 +1,141 @@
import { Controller, useFormContext, useWatch } from "react-hook-form";
import { SingleValue } from "react-select";
import { SecretSyncConnectionField } from "@app/components/secret-syncs/forms/SecretSyncConnectionField";
import { FilterableSelect, FormControl } from "@app/components/v2";
import {
TBitbucketEnvironment,
TBitbucketRepo,
TBitbucketWorkspace,
useBitbucketConnectionListEnvironments,
useBitbucketConnectionListRepositories,
useBitbucketConnectionListWorkspaces
} from "@app/hooks/api/appConnections/bitbucket";
import { SecretSync } from "@app/hooks/api/secretSyncs";
import { TSecretSyncForm } from "../schemas";
export const BitbucketSyncFields = () => {
const { control, setValue } = useFormContext<
TSecretSyncForm & { destination: SecretSync.Bitbucket }
>();
const connectionId = useWatch({ name: "connection.id", control });
const workspace = useWatch({ name: "destinationConfig.workspaceSlug", control });
const repository = useWatch({ name: "destinationConfig.repositorySlug", control });
const { data: workspaces = [], isPending: isWorkspacesLoading } =
useBitbucketConnectionListWorkspaces(connectionId, {
enabled: Boolean(connectionId)
});
const { data: repositories = [], isPending: isRepositoriesLoading } =
useBitbucketConnectionListRepositories(connectionId, workspace ?? "", {
enabled: Boolean(connectionId) && Boolean(workspace)
});
const { data: environments = [], isPending: isEnvironmentsLoading } =
useBitbucketConnectionListEnvironments(connectionId, workspace ?? "", repository ?? "", {
enabled: Boolean(connectionId) && Boolean(workspace) && Boolean(repository)
});
return (
<>
<SecretSyncConnectionField
onChange={() => {
setValue("destinationConfig.workspaceSlug", "");
setValue("destinationConfig.repositorySlug", "");
setValue("destinationConfig.environmentId", "");
}}
/>
<Controller
name="destinationConfig.workspaceSlug"
control={control}
render={({ field: { value, onChange }, fieldState: { error } }) => (
<FormControl
isError={Boolean(error)}
errorText={error?.message}
label="Bitbucket Workspace"
tooltipClassName="max-w-md"
>
<FilterableSelect
isLoading={isWorkspacesLoading && Boolean(connectionId)}
isDisabled={!connectionId}
value={workspaces.find((w) => w.slug === value) ?? null}
onChange={(option) => {
const v = option as SingleValue<TBitbucketWorkspace>;
onChange(v?.slug ?? "");
// Clear downstream selections
setValue("destinationConfig.repositorySlug", "");
setValue("destinationConfig.environmentId", "");
}}
options={workspaces}
placeholder="Select workspace..."
getOptionLabel={(option) => option.slug}
getOptionValue={(option) => option.slug}
/>
</FormControl>
)}
/>
<Controller
name="destinationConfig.repositorySlug"
control={control}
render={({ field: { value, onChange }, fieldState: { error } }) => (
<FormControl
isError={Boolean(error)}
errorText={error?.message}
label="Bitbucket Repository"
tooltipClassName="max-w-md"
>
<FilterableSelect
isLoading={isRepositoriesLoading && Boolean(workspace)}
isDisabled={!workspace}
value={repositories.find((r) => r.slug === value) ?? null}
onChange={(option) => {
const v = option as SingleValue<TBitbucketRepo>;
onChange(v?.slug ?? "");
// Clear downstream selections
setValue("destinationConfig.environmentId", "");
}}
options={repositories}
placeholder="Select repository..."
getOptionLabel={(option) => option.full_name}
getOptionValue={(option) => option.slug}
/>
</FormControl>
)}
/>
<Controller
name="destinationConfig.environmentId"
control={control}
render={({ field: { value, onChange }, fieldState: { error } }) => (
<FormControl
isError={Boolean(error)}
errorText={error?.message}
isOptional
label="Bitbucket Deployment Environment"
tooltipClassName="max-w-md"
>
<FilterableSelect
isLoading={isEnvironmentsLoading && Boolean(repository)}
isDisabled={!repository}
value={environments.find((e) => e.uuid === value) ?? null}
onChange={(option) => {
const v = option as SingleValue<TBitbucketEnvironment>;
onChange(v?.uuid ?? "");
}}
options={environments}
placeholder="Select environment..."
getOptionLabel={(option) => option.name}
getOptionValue={(option) => option.uuid}
isClearable
/>
</FormControl>
)}
/>
</>
);
};
@@ -0,0 +1,63 @@
import { Controller, useFormContext, useWatch } from "react-hook-form";
import { SingleValue } from "react-select";
import { SecretSyncConnectionField } from "@app/components/secret-syncs/forms/SecretSyncConnectionField";
import { FilterableSelect, FormControl } from "@app/components/v2";
import { useDigitalOceanConnectionListApps } from "@app/hooks/api/appConnections/digital-ocean";
import { TDigitalOceanApp } from "@app/hooks/api/appConnections/digital-ocean/types";
import { SecretSync } from "@app/hooks/api/secretSyncs";
import { TSecretSyncForm } from "../schemas";
export const DigitalOceanAppPlatformSyncFields = () => {
const { control, setValue } = useFormContext<
TSecretSyncForm & { destination: SecretSync.DigitalOceanAppPlatform }
>();
const connectionId = useWatch({ name: "connection.id", control });
const { data: apps = [], isPending: isAccountsLoading } = useDigitalOceanConnectionListApps(
connectionId,
{
enabled: Boolean(connectionId)
}
);
return (
<>
<SecretSyncConnectionField
onChange={() => {
setValue("destinationConfig.appId", "");
setValue("destinationConfig.appName", "");
}}
/>
<Controller
name="destinationConfig.appId"
control={control}
render={({ field: { value, onChange }, fieldState: { error } }) => (
<FormControl
isError={Boolean(error)}
errorText={error?.message}
label="Select an app"
tooltipClassName="max-w-md"
>
<FilterableSelect
isLoading={isAccountsLoading && Boolean(connectionId)}
isDisabled={!connectionId}
value={apps.find((p) => p.id === value) ?? null}
onChange={(option) => {
const v = option as SingleValue<TDigitalOceanApp>;
onChange(v?.id ?? null);
setValue("destinationConfig.appName", v?.spec.name ?? "");
}}
options={apps}
placeholder="Select an app..."
getOptionLabel={(option) => option.spec.name}
getOptionValue={(option) => option.id}
/>
</FormControl>
)}
/>
</>
);
};
@@ -9,11 +9,13 @@ import { AwsSecretsManagerSyncFields } from "./AwsSecretsManagerSyncFields";
import { AzureAppConfigurationSyncFields } from "./AzureAppConfigurationSyncFields";
import { AzureDevOpsSyncFields } from "./AzureDevOpsSyncFields";
import { AzureKeyVaultSyncFields } from "./AzureKeyVaultSyncFields";
import { BitbucketSyncFields } from "./BitbucketSyncFields";
import { CamundaSyncFields } from "./CamundaSyncFields";
import { ChecklySyncFields } from "./ChecklySyncFields";
import { CloudflarePagesSyncFields } from "./CloudflarePagesSyncFields";
import { CloudflareWorkersSyncFields } from "./CloudflareWorkersSyncFields";
import { DatabricksSyncFields } from "./DatabricksSyncFields";
import { DigitalOceanAppPlatformSyncFields } from "./DigitalOceanAppPlatformSyncFields";
import { FlyioSyncFields } from "./FlyioSyncFields";
import { GcpSyncFields } from "./GcpSyncFields";
import { GitHubSyncFields } from "./GitHubSyncFields";
@@ -91,6 +93,10 @@ export const SecretSyncDestinationFields = () => {
return <ChecklySyncFields />;
case SecretSync.Supabase:
return <SupabaseSyncFields />;
case SecretSync.DigitalOceanAppPlatform:
return <DigitalOceanAppPlatformSyncFields />;
case SecretSync.Bitbucket:
return <BitbucketSyncFields />;
default:
throw new Error(`Unhandled Destination Config Field: ${destination}`);
}
@@ -63,6 +63,8 @@ export const SecretSyncOptionsFields = ({ hideInitialSync }: Props) => {
case SecretSync.Railway:
case SecretSync.Checkly:
case SecretSync.Supabase:
case SecretSync.DigitalOceanAppPlatform:
case SecretSync.Bitbucket:
AdditionalSyncOptionsFieldsComponent = null;
break;
default:
@@ -0,0 +1,20 @@
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 BitbucketSyncReviewFields = () => {
const { watch } = useFormContext<TSecretSyncForm & { destination: SecretSync.Bitbucket }>();
const repository = watch("destinationConfig.repositorySlug");
const environment = watch("destinationConfig.environmentId");
const workspace = watch("destinationConfig.workspaceSlug");
return (
<>
<GenericFieldLabel label="Repository">{repository}</GenericFieldLabel>
<GenericFieldLabel label="Environment">{environment}</GenericFieldLabel>
<GenericFieldLabel label="Workspace">{workspace}</GenericFieldLabel>
</>
);
};
@@ -0,0 +1,14 @@
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 DigitalOceanAppPlatformSyncReviewFields = () => {
const { watch } = useFormContext<
TSecretSyncForm & { destination: SecretSync.DigitalOceanAppPlatform }
>();
const appName = watch("destinationConfig.appName");
return <GenericFieldLabel label="App">{appName}</GenericFieldLabel>;
};
@@ -18,11 +18,13 @@ import {
import { AzureAppConfigurationSyncReviewFields } from "./AzureAppConfigurationSyncReviewFields";
import { AzureDevOpsSyncReviewFields } from "./AzureDevOpsSyncReviewFields";
import { AzureKeyVaultSyncReviewFields } from "./AzureKeyVaultSyncReviewFields";
import { BitbucketSyncReviewFields } from "./BitbucketSyncReviewFields";
import { CamundaSyncReviewFields } from "./CamundaSyncReviewFields";
import { ChecklySyncReviewFields } from "./ChecklySyncReviewFields";
import { CloudflarePagesSyncReviewFields } from "./CloudflarePagesReviewFields";
import { CloudflareWorkersSyncReviewFields } from "./CloudflareWorkersReviewFields";
import { DatabricksSyncReviewFields } from "./DatabricksSyncReviewFields";
import { DigitalOceanAppPlatformSyncReviewFields } from "./DigitalOceanAppPlatformSyncReviewFields";
import { FlyioSyncReviewFields } from "./FlyioSyncReviewFields";
import { GcpSyncReviewFields } from "./GcpSyncReviewFields";
import { GitHubSyncReviewFields } from "./GitHubSyncReviewFields";
@@ -144,6 +146,12 @@ export const SecretSyncReviewFields = () => {
case SecretSync.Supabase:
DestinationFieldsComponent = <SupabaseSyncReviewFields />;
break;
case SecretSync.DigitalOceanAppPlatform:
DestinationFieldsComponent = <DigitalOceanAppPlatformSyncReviewFields />;
break;
case SecretSync.Bitbucket:
DestinationFieldsComponent = <BitbucketSyncReviewFields />;
break;
default:
throw new Error(`Unhandled Destination Review Fields: ${destination}`);
}
@@ -0,0 +1,19 @@
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 BitbucketSyncDestinationSchema = BaseSecretSyncSchema().merge(
z.object({
destination: z.literal(SecretSync.Bitbucket),
destinationConfig: z.object({
repositorySlug: z
.string()
.trim()
.min(1, "Repository slug required")
.describe("Repository slug"),
environmentId: z.string().trim().optional().describe("Deployment environment uuid"),
workspaceSlug: z.string().trim().min(1, "Workspace slug required").describe("Workspace slug")
})
})
);
@@ -0,0 +1,20 @@
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 DigitalOceanAppPlatformSyncDestinationSchema = BaseSecretSyncSchema().merge(
z.object({
destination: z.literal(SecretSync.DigitalOceanAppPlatform),
destinationConfig: z.object({
appId: z
.string()
.min(1, "App ID is required")
.max(255, "App ID must be less than 255 characters"),
appName: z
.string()
.min(1, "Account Name is required")
.max(255, "App Name must be less than 255 characters")
})
})
);
@@ -6,11 +6,13 @@ import { AwsSecretsManagerSyncDestinationSchema } from "./aws-secrets-manager-sy
import { AzureAppConfigurationSyncDestinationSchema } from "./azure-app-configuration-sync-destination-schema";
import { AzureDevOpsSyncDestinationSchema } from "./azure-devops-sync-destination-schema";
import { AzureKeyVaultSyncDestinationSchema } from "./azure-key-vault-sync-destination-schema";
import { BitbucketSyncDestinationSchema } from "./bitbucket-sync-destination-schema";
import { CamundaSyncDestinationSchema } from "./camunda-sync-destination-schema";
import { ChecklySyncDestinationSchema } from "./checkly-sync-destination-schema";
import { CloudflarePagesSyncDestinationSchema } from "./cloudflare-pages-sync-destination-schema";
import { CloudflareWorkersSyncDestinationSchema } from "./cloudflare-workers-sync-destination-schema";
import { DatabricksSyncDestinationSchema } from "./databricks-sync-destination-schema";
import { DigitalOceanAppPlatformSyncDestinationSchema } from "./digital-ocean-app-platform-sync-destination-schema";
import { FlyioSyncDestinationSchema } from "./flyio-sync-destination-schema";
import { GcpSyncDestinationSchema } from "./gcp-sync-destination-schema";
import { GitHubSyncDestinationSchema } from "./github-sync-destination-schema";
@@ -55,7 +57,9 @@ const SecretSyncUnionSchema = z.discriminatedUnion("destination", [
SupabaseSyncDestinationSchema,
ZabbixSyncDestinationSchema,
RailwaySyncDestinationSchema,
ChecklySyncDestinationSchema
ChecklySyncDestinationSchema,
DigitalOceanAppPlatformSyncDestinationSchema,
BitbucketSyncDestinationSchema
]);
export const SecretSyncFormSchema = SecretSyncUnionSchema;
@@ -20,6 +20,7 @@ export type DatePickerProps = Omit<DayPickerProps, "selected"> & {
popUpProps: PopoverProps;
popUpContentProps: PopoverContentProps;
dateFormat?: "PPP" | "PP" | "P"; // extend as needed
hideTime?: boolean;
buttonClassName?: string;
timezone?: Timezone;
};
@@ -55,6 +56,7 @@ export const DatePicker = ({
popUpProps,
popUpContentProps,
dateFormat = "PPP",
hideTime = false,
buttonClassName,
timezone,
...props
@@ -124,14 +126,16 @@ export const DatePicker = ({
}}
/>
</div>
<div className="mx-4 my-4">
<Input
type="time"
value={displayUtc ? localTimeToUTC(timeValue) : timeValue}
onChange={handleTimeChange}
className="bg-mineshaft-700 text-white [color-scheme:dark]"
/>
</div>
{!hideTime && (
<div className="mx-4 my-4">
<Input
type="time"
value={displayUtc ? localTimeToUTC(timeValue) : timeValue}
onChange={handleTimeChange}
className="bg-mineshaft-700 text-white [color-scheme:dark]"
/>
</div>
)}
</PopoverContent>
</Popover>
);
+132 -132
View File
@@ -70,276 +70,276 @@ export const ROUTE_PATHS = Object.freeze({
},
SecretManager: {
ApprovalPage: setRoute(
"/projects/$projectId/secret-manager/approval",
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/secret-manager/_secret-manager-layout/approval"
"/projects/secret-management/$projectId/approval",
"/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/approval"
),
SecretDashboardPage: setRoute(
"/projects/$projectId/secret-manager/secrets/$envSlug",
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/secret-manager/_secret-manager-layout/secrets/$envSlug"
"/projects/secret-management/$projectId/secrets/$envSlug",
"/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/secrets/$envSlug"
),
RollbackPreviewPage: setRoute(
"/projects/$projectId/secret-manager/commits/$environment/$folderId/$commitId/restore",
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/secret-manager/_secret-manager-layout/commits/$environment/$folderId/$commitId/restore"
"/projects/secret-management/$projectId/commits/$environment/$folderId/$commitId/restore",
"/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/commits/$environment/$folderId/$commitId/restore"
),
CommitDetailsPage: setRoute(
"/projects/$projectId/secret-manager/commits/$environment/$folderId/$commitId",
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/secret-manager/_secret-manager-layout/commits/$environment/$folderId/$commitId"
"/projects/secret-management/$projectId/commits/$environment/$folderId/$commitId",
"/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/commits/$environment/$folderId/$commitId"
),
CommitsPage: setRoute(
"/projects/$projectId/secret-manager/commits/$environment/$folderId",
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/secret-manager/_secret-manager-layout/commits/$environment/$folderId"
"/projects/secret-management/$projectId/commits/$environment/$folderId",
"/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/commits/$environment/$folderId"
),
OverviewPage: setRoute(
"/projects/$projectId/secret-manager/overview",
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/secret-manager/_secret-manager-layout/overview"
"/projects/secret-management/$projectId/overview",
"/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/overview"
),
IntegrationsListPage: setRoute(
"/projects/$projectId/secret-manager/integrations",
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/secret-manager/_secret-manager-layout/integrations/"
"/projects/secret-management/$projectId/integrations",
"/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/integrations/"
),
IntegrationDetailsByIDPage: setRoute(
"/projects/$projectId/secret-manager/integrations/$integrationId",
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/secret-manager/_secret-manager-layout/integrations/$integrationId"
"/projects/secret-management/$projectId/integrations/$integrationId",
"/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/integrations/$integrationId"
),
SecretSyncDetailsByIDPage: setRoute(
"/projects/$projectId/secret-manager/integrations/secret-syncs/$destination/$syncId",
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/secret-manager/_secret-manager-layout/integrations/secret-syncs/$destination/$syncId"
"/projects/secret-management/$projectId/integrations/secret-syncs/$destination/$syncId",
"/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/integrations/secret-syncs/$destination/$syncId"
),
Integratons: {
SelectIntegrationAuth: setRoute(
"/projects/$projectId/secret-manager/integrations/select-integration-auth",
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/secret-manager/_secret-manager-layout/integrations/select-integration-auth"
"/projects/secret-management/$projectId/integrations/select-integration-auth",
"/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/integrations/select-integration-auth"
),
HerokuOauthCallbackPage: setRoute(
"/projects/$projectId/secret-manager/integrations/heroku/oauth2/callback",
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/secret-manager/_secret-manager-layout/integrations/heroku/oauth2/callback"
"/projects/secret-management/$projectId/integrations/heroku/oauth2/callback",
"/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/integrations/heroku/oauth2/callback"
),
HerokuConfigurePage: setRoute(
"/projects/$projectId/secret-manager/integrations/heroku/create",
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/secret-manager/_secret-manager-layout/integrations/heroku/create"
"/projects/secret-management/$projectId/integrations/heroku/create",
"/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/integrations/heroku/create"
),
AwsParameterStoreConfigurePage: setRoute(
"/projects/$projectId/secret-manager/integrations/aws-parameter-store/create",
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/secret-manager/_secret-manager-layout/integrations/aws-parameter-store/create"
"/projects/secret-management/$projectId/integrations/aws-parameter-store/create",
"/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/integrations/aws-parameter-store/create"
),
AwsSecretManagerConfigurePage: setRoute(
"/projects/$projectId/secret-manager/integrations/aws-secret-manager/create",
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/secret-manager/_secret-manager-layout/integrations/aws-secret-manager/create"
"/projects/secret-management/$projectId/integrations/aws-secret-manager/create",
"/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/integrations/aws-secret-manager/create"
),
AzureAppConfigurationsOauthCallbackPage: setRoute(
"/projects/$projectId/secret-manager/integrations/azure-app-configuration/oauth2/callback",
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/secret-manager/_secret-manager-layout/integrations/azure-app-configuration/oauth2/callback"
"/projects/secret-management/$projectId/integrations/azure-app-configuration/oauth2/callback",
"/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/integrations/azure-app-configuration/oauth2/callback"
),
AzureAppConfigurationsConfigurePage: setRoute(
"/projects/$projectId/secret-manager/integrations/azure-app-configuration/create",
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/secret-manager/_secret-manager-layout/integrations/azure-app-configuration/create"
"/projects/secret-management/$projectId/integrations/azure-app-configuration/create",
"/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/integrations/azure-app-configuration/create"
),
AzureDevopsConfigurePage: setRoute(
"/projects/$projectId/secret-manager/integrations/azure-devops/create",
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/secret-manager/_secret-manager-layout/integrations/azure-devops/create"
"/projects/secret-management/$projectId/integrations/azure-devops/create",
"/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/integrations/azure-devops/create"
),
AzureKeyVaultAuthorizePage: setRoute(
"/projects/$projectId/secret-manager/integrations/azure-key-vault/authorize",
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/secret-manager/_secret-manager-layout/integrations/azure-key-vault/authorize"
"/projects/secret-management/$projectId/integrations/azure-key-vault/authorize",
"/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/integrations/azure-key-vault/authorize"
),
AzureKeyVaultOauthCallbackPage: setRoute(
"/projects/$projectId/secret-manager/integrations/azure-key-vault/oauth2/callback",
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/secret-manager/_secret-manager-layout/integrations/azure-key-vault/oauth2/callback"
"/projects/secret-management/$projectId/integrations/azure-key-vault/oauth2/callback",
"/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/integrations/azure-key-vault/oauth2/callback"
),
AzureKeyVaultConfigurePage: setRoute(
"/projects/$projectId/secret-manager/integrations/azure-key-vault/create",
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/secret-manager/_secret-manager-layout/integrations/azure-key-vault/create"
"/projects/secret-management/$projectId/integrations/azure-key-vault/create",
"/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/integrations/azure-key-vault/create"
),
BitbucketOauthCallbackPage: setRoute(
"/projects/$projectId/secret-manager/integrations/bitbucket/oauth2/callback",
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/secret-manager/_secret-manager-layout/integrations/bitbucket/oauth2/callback"
"/projects/secret-management/$projectId/integrations/bitbucket/oauth2/callback",
"/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/integrations/bitbucket/oauth2/callback"
),
BitbucketConfigurePage: setRoute(
"/projects/$projectId/secret-manager/integrations/bitbucket/create",
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/secret-manager/_secret-manager-layout/integrations/bitbucket/create"
"/projects/secret-management/$projectId/integrations/bitbucket/create",
"/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/integrations/bitbucket/create"
),
ChecklyConfigurePage: setRoute(
"/projects/$projectId/secret-manager/integrations/checkly/create",
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/secret-manager/_secret-manager-layout/integrations/checkly/create"
"/projects/secret-management/$projectId/integrations/checkly/create",
"/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/integrations/checkly/create"
),
CircleConfigurePage: setRoute(
"/projects/$projectId/secret-manager/integrations/circleci/create",
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/secret-manager/_secret-manager-layout/integrations/circleci/create"
"/projects/secret-management/$projectId/integrations/circleci/create",
"/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/integrations/circleci/create"
),
CloudflarePagesConfigurePage: setRoute(
"/projects/$projectId/secret-manager/integrations/cloudflare-pages/create",
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/secret-manager/_secret-manager-layout/integrations/cloudflare-pages/create"
"/projects/secret-management/$projectId/integrations/cloudflare-pages/create",
"/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/integrations/cloudflare-pages/create"
),
DigitalOceanAppPlatformConfigurePage: setRoute(
"/projects/$projectId/secret-manager/integrations/digital-ocean-app-platform/create",
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/secret-manager/_secret-manager-layout/integrations/digital-ocean-app-platform/create"
"/projects/secret-management/$projectId/integrations/digital-ocean-app-platform/create",
"/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/integrations/digital-ocean-app-platform/create"
),
CloudflareWorkersConfigurePage: setRoute(
"/projects/$projectId/secret-manager/integrations/cloudflare-workers/create",
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/secret-manager/_secret-manager-layout/integrations/cloudflare-workers/create"
"/projects/secret-management/$projectId/integrations/cloudflare-workers/create",
"/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/integrations/cloudflare-workers/create"
),
CodefreshConfigurePage: setRoute(
"/projects/$projectId/secret-manager/integrations/codefresh/create",
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/secret-manager/_secret-manager-layout/integrations/codefresh/create"
"/projects/secret-management/$projectId/integrations/codefresh/create",
"/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/integrations/codefresh/create"
),
GcpSecretManagerConfigurePage: setRoute(
"/projects/$projectId/secret-manager/integrations/gcp-secret-manager/create",
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/secret-manager/_secret-manager-layout/integrations/gcp-secret-manager/create"
"/projects/secret-management/$projectId/integrations/gcp-secret-manager/create",
"/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/integrations/gcp-secret-manager/create"
),
GcpSecretManagerOauthCallbackPage: setRoute(
"/projects/$projectId/secret-manager/integrations/gcp-secret-manager/oauth2/callback",
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/secret-manager/_secret-manager-layout/integrations/gcp-secret-manager/oauth2/callback"
"/projects/secret-management/$projectId/integrations/gcp-secret-manager/oauth2/callback",
"/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/integrations/gcp-secret-manager/oauth2/callback"
),
GithubConfigurePage: setRoute(
"/projects/$projectId/secret-manager/integrations/github/create",
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/secret-manager/_secret-manager-layout/integrations/github/create"
"/projects/secret-management/$projectId/integrations/github/create",
"/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/integrations/github/create"
),
GithubOauthCallbackPage: setRoute(
"/projects/$projectId/secret-manager/integrations/github/oauth2/callback",
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/secret-manager/_secret-manager-layout/integrations/github/oauth2/callback"
"/projects/secret-management/$projectId/integrations/github/oauth2/callback",
"/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/integrations/github/oauth2/callback"
),
GitlabConfigurePage: setRoute(
"/projects/$projectId/secret-manager/integrations/gitlab/create",
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/secret-manager/_secret-manager-layout/integrations/gitlab/create"
"/projects/secret-management/$projectId/integrations/gitlab/create",
"/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/integrations/gitlab/create"
),
GitlabOauthCallbackPage: setRoute(
"/projects/$projectId/secret-manager/integrations/gitlab/oauth2/callback",
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/secret-manager/_secret-manager-layout/integrations/gitlab/oauth2/callback"
"/projects/secret-management/$projectId/integrations/gitlab/oauth2/callback",
"/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/integrations/gitlab/oauth2/callback"
),
VercelOauthCallbackPage: setRoute(
"/projects/$projectId/secret-manager/integrations/vercel/oauth2/callback",
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/secret-manager/_secret-manager-layout/integrations/vercel/oauth2/callback"
"/projects/secret-management/$projectId/integrations/vercel/oauth2/callback",
"/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/integrations/vercel/oauth2/callback"
),
VercelConfigurePage: setRoute(
"/projects/$projectId/secret-manager/integrations/vercel/create",
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/secret-manager/_secret-manager-layout/integrations/vercel/create"
"/projects/secret-management/$projectId/integrations/vercel/create",
"/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/integrations/vercel/create"
),
FlyioConfigurePage: setRoute(
"/projects/$projectId/secret-manager/integrations/flyio/create",
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/secret-manager/_secret-manager-layout/integrations/flyio/create"
"/projects/secret-management/$projectId/integrations/flyio/create",
"/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/integrations/flyio/create"
),
HashicorpVaultConfigurePage: setRoute(
"/projects/$projectId/secret-manager/integrations/hashicorp-vault/create",
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/secret-manager/_secret-manager-layout/integrations/hashicorp-vault/create"
"/projects/secret-management/$projectId/integrations/hashicorp-vault/create",
"/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/integrations/hashicorp-vault/create"
),
HasuraCloudConfigurePage: setRoute(
"/projects/$projectId/secret-manager/integrations/hasura-cloud/create",
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/secret-manager/_secret-manager-layout/integrations/hasura-cloud/create"
"/projects/secret-management/$projectId/integrations/hasura-cloud/create",
"/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/integrations/hasura-cloud/create"
),
LaravelForgeConfigurePage: setRoute(
"/projects/$projectId/secret-manager/integrations/laravel-forge/create",
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/secret-manager/_secret-manager-layout/integrations/laravel-forge/create"
"/projects/secret-management/$projectId/integrations/laravel-forge/create",
"/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/integrations/laravel-forge/create"
),
NorthflankConfigurePage: setRoute(
"/projects/$projectId/secret-manager/integrations/northflank/create",
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/secret-manager/_secret-manager-layout/integrations/northflank/create"
"/projects/secret-management/$projectId/integrations/northflank/create",
"/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/integrations/northflank/create"
),
RailwayConfigurePage: setRoute(
"/projects/$projectId/secret-manager/integrations/railway/create",
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/secret-manager/_secret-manager-layout/integrations/railway/create"
"/projects/secret-management/$projectId/integrations/railway/create",
"/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/integrations/railway/create"
),
RenderConfigurePage: setRoute(
"/projects/$projectId/secret-manager/integrations/render/create",
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/secret-manager/_secret-manager-layout/integrations/render/create"
"/projects/secret-management/$projectId/integrations/render/create",
"/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/integrations/render/create"
),
RundeckConfigurePage: setRoute(
"/projects/$projectId/secret-manager/integrations/rundeck/create",
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/secret-manager/_secret-manager-layout/integrations/rundeck/create"
"/projects/secret-management/$projectId/integrations/rundeck/create",
"/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/integrations/rundeck/create"
),
WindmillConfigurePage: setRoute(
"/projects/$projectId/secret-manager/integrations/windmill/create",
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/secret-manager/_secret-manager-layout/integrations/windmill/create"
"/projects/secret-management/$projectId/integrations/windmill/create",
"/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/integrations/windmill/create"
),
TravisCIConfigurePage: setRoute(
"/projects/$projectId/secret-manager/integrations/travisci/create",
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/secret-manager/_secret-manager-layout/integrations/travisci/create"
"/projects/secret-management/$projectId/integrations/travisci/create",
"/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/integrations/travisci/create"
),
TerraformCloudConfigurePage: setRoute(
"/projects/$projectId/secret-manager/integrations/terraform-cloud/create",
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/secret-manager/_secret-manager-layout/integrations/terraform-cloud/create"
"/projects/secret-management/$projectId/integrations/terraform-cloud/create",
"/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/integrations/terraform-cloud/create"
),
TeamcityConfigurePage: setRoute(
"/projects/$projectId/secret-manager/integrations/teamcity/create",
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/secret-manager/_secret-manager-layout/integrations/teamcity/create"
"/projects/secret-management/$projectId/integrations/teamcity/create",
"/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/integrations/teamcity/create"
),
SupabaseConfigurePage: setRoute(
"/projects/$projectId/secret-manager/integrations/supabase/create",
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/secret-manager/_secret-manager-layout/integrations/supabase/create"
"/projects/secret-management/$projectId/integrations/supabase/create",
"/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/integrations/supabase/create"
),
OctopusDeployCloudConfigurePage: setRoute(
"/projects/$projectId/secret-manager/integrations/octopus-deploy/create",
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/secret-manager/_secret-manager-layout/integrations/octopus-deploy/create"
"/projects/secret-management/$projectId/integrations/octopus-deploy/create",
"/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/integrations/octopus-deploy/create"
),
DatabricksConfigurePage: setRoute(
"/projects/$projectId/secret-manager/integrations/databricks/create",
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/secret-manager/_secret-manager-layout/integrations/databricks/create"
"/projects/secret-management/$projectId/integrations/databricks/create",
"/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/integrations/databricks/create"
),
QoveryConfigurePage: setRoute(
"/projects/$projectId/secret-manager/integrations/qovery/create",
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/secret-manager/_secret-manager-layout/integrations/qovery/create"
"/projects/secret-management/$projectId/integrations/qovery/create",
"/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/integrations/qovery/create"
),
Cloud66ConfigurePage: setRoute(
"/projects/$projectId/secret-manager/integrations/cloud-66/create",
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/secret-manager/_secret-manager-layout/integrations/cloud-66/create"
"/projects/secret-management/$projectId/integrations/cloud-66/create",
"/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/integrations/cloud-66/create"
),
NetlifyConfigurePage: setRoute(
"/projects/$projectId/secret-manager/integrations/netlify/create",
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/secret-manager/_secret-manager-layout/integrations/netlify/create"
"/projects/secret-management/$projectId/integrations/netlify/create",
"/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/integrations/netlify/create"
),
NetlifyOuathCallbackPage: setRoute(
"/projects/$projectId/secret-manager/integrations/netlify/oauth2/callback",
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/secret-manager/_secret-manager-layout/integrations/netlify/oauth2/callback"
"/projects/secret-management/$projectId/integrations/netlify/oauth2/callback",
"/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/integrations/netlify/oauth2/callback"
)
}
},
CertManager: {
CertAuthDetailsByIDPage: setRoute(
"/projects/$projectId/cert-manager/ca/$caName",
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/cert-manager/_cert-manager-layout/ca/$caName"
"/projects/cert-management/$projectId/ca/$caName",
"/_authenticate/_inject-org-details/_org-layout/projects/cert-management/$projectId/_cert-manager-layout/ca/$caName"
),
SubscribersPage: setRoute(
"/projects/$projectId/cert-manager/subscribers",
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/cert-manager/_cert-manager-layout/subscribers"
"/projects/cert-management/$projectId/subscribers",
"/_authenticate/_inject-org-details/_org-layout/projects/cert-management/$projectId/_cert-manager-layout/subscribers"
),
CertificatesPage: setRoute(
"/projects/$projectId/cert-manager/certificates",
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/cert-manager/_cert-manager-layout/certificates"
"/projects/cert-management/$projectId/certificates",
"/_authenticate/_inject-org-details/_org-layout/projects/cert-management/$projectId/_cert-manager-layout/certificates"
),
CertificateAuthoritiesPage: setRoute(
"/projects/$projectId/cert-manager/certificate-authorities",
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/cert-manager/_cert-manager-layout/certificate-authorities"
"/projects/cert-management/$projectId/certificate-authorities",
"/_authenticate/_inject-org-details/_org-layout/projects/cert-management/$projectId/_cert-manager-layout/certificate-authorities"
),
AlertingPage: setRoute(
"/projects/$projectId/cert-manager/alerting",
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/cert-manager/_cert-manager-layout/alerting"
"/projects/cert-management/$projectId/alerting",
"/_authenticate/_inject-org-details/_org-layout/projects/cert-management/$projectId/_cert-manager-layout/alerting"
),
PkiCollectionDetailsByIDPage: setRoute(
"/projects/$projectId/cert-manager/pki-collections/$collectionId",
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/cert-manager/_cert-manager-layout/pki-collections/$collectionId"
"/projects/cert-management/$projectId/pki-collections/$collectionId",
"/_authenticate/_inject-org-details/_org-layout/projects/cert-management/$projectId/_cert-manager-layout/pki-collections/$collectionId"
),
PkiSubscriberDetailsByIDPage: setRoute(
"/projects/$projectId/cert-manager/subscribers/$subscriberName",
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/cert-manager/_cert-manager-layout/subscribers/$subscriberName"
"/projects/cert-management/$projectId/subscribers/$subscriberName",
"/_authenticate/_inject-org-details/_org-layout/projects/cert-management/$projectId/_cert-manager-layout/subscribers/$subscriberName"
)
},
Ssh: {
SshCaByIDPage: setRoute(
"/projects/$projectId/ssh/ca/$caId",
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/ssh/_ssh-layout/ca/$caId"
"/projects/ssh/$projectId/ca/$caId",
"/_authenticate/_inject-org-details/_org-layout/projects/ssh/$projectId/_ssh-layout/ca/$caId"
),
SshHostGroupDetailsByIDPage: setRoute(
"/projects/$projectId/ssh/ssh-host-groups/$sshHostGroupId",
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/ssh/_ssh-layout/ssh-host-groups/$sshHostGroupId"
"/projects/ssh/$projectId/ssh-host-groups/$sshHostGroupId",
"/_authenticate/_inject-org-details/_org-layout/projects/ssh/$projectId/_ssh-layout/ssh-host-groups/$sshHostGroupId"
)
},
SecretScanning: {
DataSourceByIdPage: setRoute(
"/projects/$projectId/secret-scanning/data-sources/$type/$dataSourceId",
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/secret-scanning/_secret-scanning-layout/data-sources/$type/$dataSourceId"
"/projects/secret-scanning/$projectId/data-sources/$type/$dataSourceId",
"/_authenticate/_inject-org-details/_org-layout/projects/secret-scanning/$projectId/_secret-scanning-layout/data-sources/$type/$dataSourceId"
),
FindingsPage: setRoute(
"/projects/$projectId/secret-scanning/findings",
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/secret-scanning/_secret-scanning-layout/findings"
"/projects/secret-scanning/$projectId/findings",
"/_authenticate/_inject-org-details/_org-layout/projects/secret-scanning/$projectId/_secret-scanning-layout/findings"
)
},
Public: {
+13 -7
View File
@@ -17,9 +17,7 @@ import {
AzureClientSecretsConnectionMethod,
AzureDevOpsConnectionMethod,
AzureKeyVaultConnectionMethod,
BitbucketConnectionMethod,
CamundaConnectionMethod,
ChecklyConnectionMethod,
CloudflareConnectionMethod,
DatabricksConnectionMethod,
FlyioConnectionMethod,
@@ -28,19 +26,14 @@ import {
GitHubRadarConnectionMethod,
GitLabConnectionMethod,
HCVaultConnectionMethod,
HerokuConnectionMethod,
HumanitecConnectionMethod,
LdapConnectionMethod,
MsSqlConnectionMethod,
MySqlConnectionMethod,
OCIConnectionMethod,
OktaConnectionMethod,
OnePassConnectionMethod,
OracleDBConnectionMethod,
PostgresConnectionMethod,
RailwayConnectionMethod,
RenderConnectionMethod,
SupabaseConnectionMethod,
TAppConnection,
TeamCityConnectionMethod,
TerraformCloudConnectionMethod,
@@ -48,6 +41,14 @@ import {
WindmillConnectionMethod,
ZabbixConnectionMethod
} from "@app/hooks/api/appConnections/types";
import { BitbucketConnectionMethod } from "@app/hooks/api/appConnections/types/bitbucket-connection";
import { ChecklyConnectionMethod } from "@app/hooks/api/appConnections/types/checkly-connection";
import { DigitalOceanConnectionMethod } from "@app/hooks/api/appConnections/types/digital-ocean";
import { HerokuConnectionMethod } from "@app/hooks/api/appConnections/types/heroku-connection";
import { OCIConnectionMethod } from "@app/hooks/api/appConnections/types/oci-connection";
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";
export const APP_CONNECTION_MAP: Record<
AppConnection,
@@ -100,6 +101,10 @@ export const APP_CONNECTION_MAP: Record<
[AppConnection.Bitbucket]: { name: "Bitbucket", image: "Bitbucket.png" },
[AppConnection.Checkly]: { name: "Checkly", image: "Checkly.png" },
[AppConnection.Supabase]: { name: "Supabase", image: "Supabase.png" },
[AppConnection.DigitalOcean]: {
name: "Digital Ocean",
image: "Digital Ocean.png"
},
[AppConnection.Okta]: { name: "Okta", image: "Okta.png" }
};
@@ -134,6 +139,7 @@ export const getAppConnectionMethodDetails = (method: TAppConnection["method"])
case CloudflareConnectionMethod.ApiToken:
case BitbucketConnectionMethod.ApiToken:
case ZabbixConnectionMethod.ApiToken:
case DigitalOceanConnectionMethod.ApiToken:
case OktaConnectionMethod.ApiToken:
return { name: "API Token", icon: faKey };
case PostgresConnectionMethod.UsernameAndPassword:
+29 -8
View File
@@ -42,7 +42,8 @@ export const initProjectHelper = async ({ projectName }: { projectName: string }
const {
data: { project }
} = await createWorkspace({
projectName
projectName,
type: ProjectType.SecretManager
});
try {
@@ -59,20 +60,34 @@ export const initProjectHelper = async ({ projectName }: { projectName: string }
return project;
};
export const getProjectBaseURL = (type: ProjectType) => {
switch (type) {
case ProjectType.SecretManager:
return "/projects/secret-management/$projectId";
case ProjectType.CertificateManager:
return "/projects/cert-management/$projectId";
default:
return `/projects/${type}/$projectId` as const;
}
};
export const getProjectHomePage = (type: ProjectType) => {
switch (type) {
case ProjectType.SecretManager:
return "/projects/secret-management/$projectId/overview";
case ProjectType.CertificateManager:
return `/projects/$projectId/${type}/subscribers` as const;
return "/projects/cert-management/$projectId/subscribers";
case ProjectType.SecretScanning:
return `/projects/$projectId/${type}/data-sources` as const;
return `/projects/${type}/$projectId/data-sources` as const;
default:
return `/projects/$projectId/${type}/overview` as const;
return `/projects/${type}/$projectId/overview` as const;
}
};
export const getProjectTitle = (type: ProjectType) => {
const titleConvert = {
[ProjectType.SecretManager]: "Secret Management",
[ProjectType.SecretManager]: "Secrets Management",
[ProjectType.KMS]: "Key Management",
[ProjectType.CertificateManager]: "Cert Management",
[ProjectType.SSH]: "SSH",
@@ -81,7 +96,13 @@ export const getProjectTitle = (type: ProjectType) => {
return titleConvert[type];
};
export const getCurrentProductFromUrl = (location: string) => {
const type = Object.values(ProjectType).find((el) => location.includes(`/${el}`));
return type;
export const getProjectLottieIcon = (type: ProjectType) => {
const titleConvert = {
[ProjectType.SecretManager]: "vault",
[ProjectType.KMS]: "unlock",
[ProjectType.CertificateManager]: "note",
[ProjectType.SSH]: "terminal",
[ProjectType.SecretScanning]: "secret-scan"
};
return titleConvert[type];
};
+11 -1
View File
@@ -101,6 +101,14 @@ export const SECRET_SYNC_MAP: Record<SecretSync, { name: string; image: string }
[SecretSync.Supabase]: {
name: "Supabase",
image: "Supabase.png"
},
[SecretSync.DigitalOceanAppPlatform]: {
name: "Digital Ocean App Platform",
image: "Digital Ocean.png"
},
[SecretSync.Bitbucket]: {
name: "Bitbucket",
image: "Bitbucket.png"
}
};
@@ -131,7 +139,9 @@ export const SECRET_SYNC_CONNECTION_MAP: Record<SecretSync, AppConnection> = {
[SecretSync.Supabase]: AppConnection.Supabase,
[SecretSync.Zabbix]: AppConnection.Zabbix,
[SecretSync.Railway]: AppConnection.Railway,
[SecretSync.Checkly]: AppConnection.Checkly
[SecretSync.Checkly]: AppConnection.Checkly,
[SecretSync.DigitalOceanAppPlatform]: AppConnection.DigitalOcean,
[SecretSync.Bitbucket]: AppConnection.Bitbucket
};
export const SECRET_SYNC_INITIAL_SYNC_BEHAVIOR_MAP: Record<
@@ -4,8 +4,10 @@ import { apiRequest } from "@app/config/request";
import { appConnectionKeys } from "../queries";
import {
TBitbucketConnectionListEnvironmentsResponse,
TBitbucketConnectionListRepositoriesResponse,
TBitbucketConnectionListWorkspacesResponse,
TBitbucketEnvironment,
TBitbucketRepo,
TBitbucketWorkspace
} from "./types";
@@ -15,7 +17,9 @@ const bitbucketConnectionKeys = {
listRepos: (connectionId: string, workspaceSlug: string) =>
[...bitbucketConnectionKeys.all, "repos", connectionId, workspaceSlug] as const,
listWorkspaces: (connectionId: string) =>
[...bitbucketConnectionKeys.all, "workspaces", connectionId] as const
[...bitbucketConnectionKeys.all, "workspaces", connectionId] as const,
listEnvironments: (connectionId: string, workspaceSlug: string, repoSlug: string) =>
[...bitbucketConnectionKeys.all, "environments", connectionId, workspaceSlug, repoSlug] as const
};
export const useBitbucketConnectionListWorkspaces = (
@@ -68,3 +72,30 @@ export const useBitbucketConnectionListRepositories = (
...options
});
};
export const useBitbucketConnectionListEnvironments = (
connectionId: string,
workspaceSlug: string,
repoSlug: string,
options?: Omit<
UseQueryOptions<
TBitbucketEnvironment[],
unknown,
TBitbucketEnvironment[],
ReturnType<typeof bitbucketConnectionKeys.listEnvironments>
>,
"queryKey" | "queryFn"
>
) => {
return useQuery({
queryKey: bitbucketConnectionKeys.listEnvironments(connectionId, workspaceSlug, repoSlug),
queryFn: async () => {
const { data } = await apiRequest.get<TBitbucketConnectionListEnvironmentsResponse>(
`/api/v1/app-connections/bitbucket/${connectionId}/environments?workspaceSlug=${encodeURIComponent(workspaceSlug)}&repositorySlug=${encodeURIComponent(repoSlug)}`
);
return data.environments;
},
...options
});
};
@@ -15,3 +15,13 @@ export type TBitbucketConnectionListWorkspacesResponse = {
export type TBitbucketConnectionListRepositoriesResponse = {
repositories: TBitbucketRepo[];
};
export type TBitbucketEnvironment = {
uuid: string;
name: string;
slug: string;
};
export type TBitbucketConnectionListEnvironmentsResponse = {
environments: TBitbucketEnvironment[];
};
@@ -0,0 +1 @@
export * from "./queries";
@@ -0,0 +1,37 @@
import { useQuery, UseQueryOptions } from "@tanstack/react-query";
import { apiRequest } from "@app/config/request";
import { appConnectionKeys } from "@app/hooks/api/appConnections";
import { TDigitalOceanApp } from "./types";
const digitalOceanAppPlatformConnectionKeys = {
all: [...appConnectionKeys.all, "digitalOceanAppPlatform"] as const,
listAccounts: (connectionId: string) =>
[...digitalOceanAppPlatformConnectionKeys.all, "workspace-scopes", connectionId] as const
};
export const useDigitalOceanConnectionListApps = (
connectionId: string,
options?: Omit<
UseQueryOptions<
TDigitalOceanApp[],
unknown,
TDigitalOceanApp[],
ReturnType<typeof digitalOceanAppPlatformConnectionKeys.listAccounts>
>,
"queryKey" | "queryFn"
>
) => {
return useQuery({
queryKey: digitalOceanAppPlatformConnectionKeys.listAccounts(connectionId),
queryFn: async () => {
const { data } = await apiRequest.get<{ apps: TDigitalOceanApp[] }>(
`/api/v1/app-connections/digital-ocean/${connectionId}/apps`
);
return data.apps;
},
...options
});
};
@@ -0,0 +1,16 @@
export type TDigitalOceanAppPlatformVariable = {
key: string;
value: string;
type: "SECRET" | "GENERAL";
};
export type TDigitalOceanApp = {
id: string;
spec: {
name: string;
services: Array<{
name: string;
}>;
envs?: TDigitalOceanAppPlatformVariable[];
};
};
@@ -33,5 +33,6 @@ export enum AppConnection {
Railway = "railway",
Checkly = "checkly",
Supabase = "supabase",
DigitalOcean = "digital-ocean",
Okta = "okta"
}
@@ -152,6 +152,10 @@ export type TSupabaseConnectionOption = TAppConnectionOptionBase & {
app: AppConnection.Supabase;
};
export type TDigitalOceanConnectionOption = TAppConnectionOptionBase & {
app: AppConnection.DigitalOcean;
};
export type TOktaConnectionOption = TAppConnectionOptionBase & {
app: AppConnection.Okta;
};
@@ -188,6 +192,7 @@ export type TAppConnectionOption =
| TZabbixConnectionOption
| TRailwayConnectionOption
| TChecklyConnectionOption
| TDigitalOceanConnectionOption
| TOktaConnectionOption;
export type TAppConnectionOptionMap = {
@@ -225,5 +230,6 @@ export type TAppConnectionOptionMap = {
[AppConnection.Railway]: TRailwayConnectionOption;
[AppConnection.Checkly]: TChecklyConnectionOption;
[AppConnection.Supabase]: TSupabaseConnectionOption;
[AppConnection.DigitalOcean]: TDigitalOceanConnectionOption;
[AppConnection.Okta]: TOktaConnectionOption;
};
@@ -0,0 +1,14 @@
import { AppConnection } from "@app/hooks/api/appConnections/enums";
import { TRootAppConnection } from "@app/hooks/api/appConnections/types/root-connection";
export enum DigitalOceanConnectionMethod {
ApiToken = "api-token"
}
export type TDigitalOceanConnection = TRootAppConnection & {
app: AppConnection.DigitalOcean;
method: DigitalOceanConnectionMethod.ApiToken;
credentials: {
apiToken: string;
};
};
@@ -12,6 +12,7 @@ import { TCamundaConnection } from "./camunda-connection";
import { TChecklyConnection } from "./checkly-connection";
import { TCloudflareConnection } from "./cloudflare-connection";
import { TDatabricksConnection } from "./databricks-connection";
import { TDigitalOceanConnection } from "./digital-ocean";
import { TFlyioConnection } from "./flyio-connection";
import { TGcpConnection } from "./gcp-connection";
import { TGitHubConnection } from "./github-connection";
@@ -107,6 +108,7 @@ export type TAppConnection =
| TRailwayConnection
| TChecklyConnection
| TSupabaseConnection
| TDigitalOceanConnection
| TOktaConnection;
export type TAvailableAppConnection = Pick<TAppConnection, "name" | "id">;
@@ -178,5 +180,6 @@ export type TAppConnectionMap = {
[AppConnection.Railway]: TRailwayConnection;
[AppConnection.Checkly]: TChecklyConnection;
[AppConnection.Supabase]: TSupabaseConnection;
[AppConnection.DigitalOcean]: TDigitalOceanConnection;
[AppConnection.Okta]: TOktaConnection;
};
+1 -1
View File
@@ -51,7 +51,7 @@ export type IdentityMembershipOrg = {
export type IdentityMembership = {
id: string;
identity: Identity;
project: Pick<Workspace, "id" | "name" | "defaultProduct">;
project: Pick<Workspace, "id" | "name" | "type">;
roles: Array<
{
id: string;
@@ -1,9 +1,12 @@
import { TProjectRole } from "@app/hooks/api/roles/types";
import { ProjectType } from "../workspace/types";
export type TProjectTemplate = {
id: string;
name: string;
description?: string;
type: ProjectType;
roles: Pick<TProjectRole, "slug" | "name" | "permissions">[];
environments?: { name: string; slug: string; position: number }[] | null;
createdAt: string;
@@ -16,6 +19,7 @@ export type TProjectTemplateResponse = { projectTemplate: TProjectTemplate };
export type TCreateProjectTemplateDTO = {
name: string;
description?: string;
type?: ProjectType;
};
export type TUpdateProjectTemplateDTO = Partial<
@@ -0,0 +1 @@
export { useCreateReminder, useDeleteReminder, useGetReminder } from "./queries";
@@ -0,0 +1,60 @@
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { apiRequest } from "@app/config/request";
import { CreateReminderDTO, DeleteReminderDTO, Reminder } from "./types";
export const reminderKeys = {
getReminder: (secretId: string) => ["get-reminder", secretId] as const
};
export const useCreateReminder = (secretId: string) => {
const queryClient = useQueryClient();
return useMutation<Reminder, object, CreateReminderDTO>({
mutationFn: async ({ message, repeatDays, nextReminderDate, recipients }) => {
const { data } = await apiRequest.post<{ reminder: Reminder }>(
`/api/v1/reminders/secrets/${secretId}`,
{
message,
repeatDays,
nextReminderDate,
recipients
}
);
return data.reminder;
},
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: reminderKeys.getReminder(secretId) });
}
});
};
export const useDeleteReminder = (secretId: string) => {
const queryClient = useQueryClient();
return useMutation<Reminder, object, DeleteReminderDTO>({
mutationFn: async () => {
const { data } = await apiRequest.delete<{ reminder: Reminder }>(
`/api/v1/reminders/secrets/${secretId}`
);
return data.reminder;
},
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: reminderKeys.getReminder(secretId) });
}
});
};
export const useGetReminder = (secretId: string) => {
return useQuery({
queryKey: reminderKeys.getReminder(secretId),
queryFn: async () => {
const { data } = await apiRequest.get<{ reminder: Reminder }>(
`/api/v1/reminders/secrets/${secretId}`
);
return data.reminder;
},
enabled: Boolean(secretId)
});
};
+14
View File
@@ -0,0 +1,14 @@
export type CreateReminderDTO = {
message?: string | null;
repeatDays?: number | null;
nextReminderDate?: Date | null;
secretId: string;
recipients?: string[];
};
export type DeleteReminderDTO = {
secretId: string;
reminderId: string;
};
export type Reminder = { id: string } & CreateReminderDTO;
+3 -1
View File
@@ -25,7 +25,9 @@ export enum SecretSync {
Supabase = "supabase",
Zabbix = "zabbix",
Railway = "railway",
Checkly = "checkly"
Checkly = "checkly",
DigitalOceanAppPlatform = "digital-ocean-app-platform",
Bitbucket = "bitbucket"
}
export enum SecretSyncStatus {
@@ -0,0 +1,17 @@
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 TBitbucketSync = TRootSecretSync & {
destination: SecretSync.Bitbucket;
destinationConfig: {
workspaceSlug: string;
repositorySlug: string;
environmentId?: string;
};
connection: {
app: AppConnection.Bitbucket;
name: string;
id: string;
};
};
@@ -0,0 +1,17 @@
/* eslint-disable @typescript-eslint/no-empty-object-type */
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 TDigitalOceanAppPlatformSync = TRootSecretSync & {
destination: SecretSync.DigitalOceanAppPlatform;
destinationConfig: {
appId: string;
appName: string;
};
connection: {
app: AppConnection.DigitalOcean;
name: string;
id: string;
};
};
@@ -8,11 +8,13 @@ import { TAwsSecretsManagerSync } from "./aws-secrets-manager-sync";
import { TAzureAppConfigurationSync } from "./azure-app-configuration-sync";
import { TAzureDevOpsSync } from "./azure-devops-sync";
import { TAzureKeyVaultSync } from "./azure-key-vault-sync";
import { TBitbucketSync } from "./bitbucket-sync";
import { TCamundaSync } from "./camunda-sync";
import { TChecklySync } from "./checkly-sync";
import { TCloudflarePagesSync } from "./cloudflare-pages-sync";
import { TCloudflareWorkersSync } from "./cloudflare-workers-sync";
import { TDatabricksSync } from "./databricks-sync";
import { TDigitalOceanAppPlatformSync } from "./digital-ocean-app-platform-sync";
import { TFlyioSync } from "./flyio-sync";
import { TGcpSync } from "./gcp-sync";
import { TGitHubSync } from "./github-sync";
@@ -63,7 +65,9 @@ export type TSecretSync =
| TZabbixSync
| TRailwaySync
| TChecklySync
| TSupabaseSync;
| TSupabaseSync
| TDigitalOceanAppPlatformSync
| TBitbucketSync;
export type TListSecretSyncs = { secretSyncs: TSecretSync[] };
+4 -5
View File
@@ -255,12 +255,13 @@ export const useCreateWorkspace = () => {
const queryClient = useQueryClient();
return useMutation<{ data: { project: Workspace } }, object, CreateWorkspaceDTO>({
mutationFn: async ({ projectName, projectDescription, kmsKeyId, template }) =>
mutationFn: async ({ projectName, projectDescription, kmsKeyId, template, type }) =>
createWorkspace({
projectName,
projectDescription,
kmsKeyId,
template
template,
type
}),
onSuccess: () => {
queryClient.invalidateQueries({
@@ -280,8 +281,7 @@ export const useUpdateProject = () => {
newProjectDescription,
newSlug,
secretSharing,
showSnapshotsLegacy,
defaultProduct
showSnapshotsLegacy
}) => {
const { data } = await apiRequest.patch<{ workspace: Workspace }>(
`/api/v1/workspace/${projectID}`,
@@ -289,7 +289,6 @@ export const useUpdateProject = () => {
name: newProjectName,
description: newProjectDescription,
slug: newSlug,
defaultProduct,
secretSharing,
showSnapshotsLegacy
}
+3 -2
View File
@@ -24,7 +24,7 @@ export type Workspace = {
__v: number;
id: string;
name: string;
defaultProduct: ProjectType;
type: ProjectType;
description?: string;
orgId: string;
version: ProjectVersion;
@@ -68,6 +68,7 @@ export type TGetUpgradeProjectStatusDTO = {
// mutation dto
export type CreateWorkspaceDTO = {
projectName: string;
type: ProjectType;
projectDescription?: string;
kmsKeyId?: string;
template?: string;
@@ -80,7 +81,6 @@ export type UpdateProjectDTO = {
newSlug?: string;
secretSharing?: boolean;
showSnapshotsLegacy?: boolean;
defaultProduct?: ProjectType;
};
export type UpdatePitVersionLimitDTO = { projectSlug: string; pitVersionLimit: number };
@@ -184,6 +184,7 @@ export type TSearchProjectsDTO = {
name?: string;
limit?: number;
offset?: number;
type?: ProjectType;
options?: { enabled?: boolean };
orderBy?: ProjectIdentityOrderBy;
orderDirection?: OrderByDirection;
+99 -28
View File
@@ -1,11 +1,16 @@
import { faCog, faCube, faHome, faLock, faUsers } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { Link, Outlet } from "@tanstack/react-router";
import { motion } from "framer-motion";
import { Menu, MenuItem } from "@app/components/v2";
import { useWorkspace } from "@app/context";
import { Lottie, Menu, MenuGroup, MenuItem } from "@app/components/v2";
import { useProjectPermission, useWorkspace } from "@app/context";
import { AssumePrivilegeModeBanner } from "../ProjectLayout/components/AssumePrivilegeModeBanner";
export const KmsLayout = () => {
const { currentWorkspace } = useWorkspace();
const { assumedPrivilegeDetails } = useProjectPermission();
return (
<div className="dark hidden h-full w-full flex-col overflow-x-hidden md:flex">
@@ -19,40 +24,106 @@ export const KmsLayout = () => {
className="dark w-full border-r border-mineshaft-600 bg-gradient-to-tr from-mineshaft-700 via-mineshaft-800 to-mineshaft-900 md:w-60"
>
<nav className="items-between flex h-full flex-col overflow-y-auto dark:[color-scheme:dark]">
<div className="border-b border-mineshaft-600 px-4 py-3.5 text-lg text-white">KMS</div>
<div className="flex items-center gap-3 border-b border-mineshaft-600 px-4 py-3.5 text-lg text-white">
<Lottie className="inline-block h-5 w-5 shrink-0" icon="unlock" />
KMS
</div>
<div className="flex-1">
<Menu>
<Link
to="/projects/$projectId/kms/overview"
params={{
projectId: currentWorkspace.id
}}
>
{({ isActive }) => <MenuItem isSelected={isActive}>Overview</MenuItem>}
<MenuGroup title="Resources">
<Link
to="/projects/kms/$projectId/overview"
params={{
projectId: currentWorkspace.id
}}
>
{({ isActive }) => (
<MenuItem isSelected={isActive}>
<div className="mx-1 flex gap-2">
<div className="w-6">
<FontAwesomeIcon icon={faCube} />
</div>
Overview
</div>
</MenuItem>
)}
</Link>
<Link
to="/projects/kms/$projectId/kmip"
params={{
projectId: currentWorkspace.id
}}
>
{({ isActive }) => (
<MenuItem isSelected={isActive}>
<div className="mx-1 flex gap-2">
<div className="w-6">
<FontAwesomeIcon icon={faLock} />
</div>
KMIP
</div>
</MenuItem>
)}
</Link>
</MenuGroup>
<MenuGroup title="Others">
<Link
to="/projects/kms/$projectId/access-management"
params={{
projectId: currentWorkspace.id
}}
>
{({ isActive }) => (
<MenuItem isSelected={isActive}>
<div className="mx-1 flex gap-2">
<div className="w-6">
<FontAwesomeIcon icon={faUsers} />
</div>
Access Management
</div>
</MenuItem>
)}
</Link>
<Link
to="/projects/kms/$projectId/settings"
params={{
projectId: currentWorkspace.id
}}
>
{({ isActive }) => (
<MenuItem isSelected={isActive}>
<div className="mx-1 flex gap-2">
<div className="w-6">
<FontAwesomeIcon icon={faCog} />
</div>
Settings
</div>
</MenuItem>
)}
</Link>
</MenuGroup>
</Menu>
</div>
<div>
<Menu>
<Link to="/organization/projects">
<MenuItem
className="relative flex items-center gap-2 overflow-hidden text-sm text-mineshaft-400 hover:text-mineshaft-300"
leftIcon={
<div className="w-6">
<FontAwesomeIcon className="mx-1 inline-block shrink-0" icon={faHome} />
</div>
}
>
Organization Home
</MenuItem>
</Link>
<Link
to="/projects/$projectId/kms/kmip"
params={{
projectId: currentWorkspace.id
}}
>
{({ isActive }) => <MenuItem isSelected={isActive}>KMIP</MenuItem>}
</Link>
{
// <Link
// to="/projects/$projectId/kms/settings"
// params={{
// projectId: currentWorkspace.id
// }}
// >
// {({ isActive }) => <MenuItem isSelected={isActive}>Settings</MenuItem>}
// </Link>
}
</Menu>
</div>
</nav>
</motion.div>
<div className="flex-1 overflow-y-auto overflow-x-hidden bg-bunker-800 p-4 pt-8">
{assumedPrivilegeDetails && <AssumePrivilegeModeBanner />}
<Outlet />
</div>
</div>
@@ -1,12 +1,12 @@
import {
faBook,
faCog,
faCubes,
faDoorClosed,
faInfinity,
faMoneyBill,
faPlug,
faShare,
faTable,
faUserCog,
faUsers,
faUserTie
@@ -56,7 +56,7 @@ export const OrgSidebar = ({ isHidden }: Props) => {
<MenuItem isSelected={isActive}>
<div className="mx-1 flex gap-2">
<div className="w-6">
<FontAwesomeIcon icon={faCubes} />
<FontAwesomeIcon icon={faTable} />
</div>
Projects
</div>
@@ -1,14 +1,27 @@
import { useTranslation } from "react-i18next";
import { faMobile } from "@fortawesome/free-solid-svg-icons";
import {
faBell,
faCertificate,
faCog,
faFileLines,
faHome,
faMobile,
faSitemap,
faStamp,
faUsers
} from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { Link, Outlet } from "@tanstack/react-router";
import { motion } from "framer-motion";
import { Menu, MenuItem } from "@app/components/v2";
import { useWorkspace } from "@app/context";
import { Lottie, Menu, MenuGroup, MenuItem } from "@app/components/v2";
import { useProjectPermission, useWorkspace } from "@app/context";
import { AssumePrivilegeModeBanner } from "../ProjectLayout/components/AssumePrivilegeModeBanner";
export const PkiManagerLayout = () => {
const { currentWorkspace } = useWorkspace();
const { assumedPrivilegeDetails } = useProjectPermission();
const { t } = useTranslation();
return (
@@ -24,70 +37,157 @@ export const PkiManagerLayout = () => {
className="dark w-full border-r border-mineshaft-600 bg-gradient-to-tr from-mineshaft-700 via-mineshaft-800 to-mineshaft-900 md:w-60"
>
<nav className="items-between flex h-full flex-col overflow-y-auto dark:[color-scheme:dark]">
<div className="border-b border-mineshaft-600 px-4 py-3.5 text-lg text-white">
<div className="flex items-center gap-3 border-b border-mineshaft-600 px-4 py-3.5 text-lg text-white">
<Lottie className="inline-block h-5 w-5 shrink-0" icon="note" />
PKI Manager
</div>
<div className="flex-1">
<Menu>
<Link
to="/projects/$projectId/cert-manager/subscribers"
params={{
projectId: currentWorkspace.id
}}
>
{({ isActive }) => <MenuItem isSelected={isActive}>Subscribers</MenuItem>}
<MenuGroup title="Resources">
<Link
to="/projects/cert-management/$projectId/subscribers"
params={{
projectId: currentWorkspace.id
}}
>
{({ isActive }) => (
<MenuItem isSelected={isActive}>
<div className="mx-1 flex gap-2">
<div className="w-6">
<FontAwesomeIcon icon={faSitemap} />
</div>
Subscribers
</div>
</MenuItem>
)}
</Link>
<Link
to="/projects/cert-management/$projectId/certificate-templates"
params={{
projectId: currentWorkspace.id
}}
>
{({ isActive }) => (
<MenuItem isSelected={isActive}>
<div className="mx-1 flex gap-2">
<div className="w-6">
<FontAwesomeIcon icon={faFileLines} />
</div>
Certificate Templates
</div>
</MenuItem>
)}
</Link>
<Link
to="/projects/cert-management/$projectId/certificates"
params={{
projectId: currentWorkspace.id
}}
>
{({ isActive }) => (
<MenuItem isSelected={isActive}>
<div className="mx-1 flex gap-2">
<div className="w-6">
<FontAwesomeIcon icon={faCertificate} />
</div>
Certificates
</div>
</MenuItem>
)}
</Link>
<Link
to="/projects/cert-management/$projectId/certificate-authorities"
params={{
projectId: currentWorkspace.id
}}
>
{({ isActive }) => (
<MenuItem isSelected={isActive}>
<div className="mx-1 flex gap-2">
<div className="w-6">
<FontAwesomeIcon icon={faStamp} />
</div>
Certificates Authority
</div>
</MenuItem>
)}
</Link>
<Link
to="/projects/cert-management/$projectId/alerting"
params={{
projectId: currentWorkspace.id
}}
>
{({ isActive }) => (
<MenuItem isSelected={isActive}>
<div className="mx-1 flex gap-2">
<div className="w-6">
<FontAwesomeIcon icon={faBell} />
</div>
Alerting
</div>
</MenuItem>
)}
</Link>
</MenuGroup>
<MenuGroup title="Others">
<Link
to="/projects/cert-management/$projectId/access-management"
params={{
projectId: currentWorkspace.id
}}
>
{({ isActive }) => (
<MenuItem isSelected={isActive}>
<div className="mx-1 flex gap-2">
<div className="w-6">
<FontAwesomeIcon icon={faUsers} />
</div>
Access Management
</div>
</MenuItem>
)}
</Link>
<Link
to="/projects/cert-management/$projectId/settings"
params={{
projectId: currentWorkspace.id
}}
>
{({ isActive }) => (
<MenuItem isSelected={isActive}>
<div className="mx-1 flex gap-2">
<div className="w-6">
<FontAwesomeIcon icon={faCog} />
</div>
Settings
</div>
</MenuItem>
)}
</Link>
</MenuGroup>
</Menu>
</div>
<div>
<Menu>
<Link to="/organization/projects">
<MenuItem
className="relative flex items-center gap-2 overflow-hidden text-sm text-mineshaft-400 hover:text-mineshaft-300"
leftIcon={
<div className="w-6">
<FontAwesomeIcon className="mx-1 inline-block shrink-0" icon={faHome} />
</div>
}
>
Organization Home
</MenuItem>
</Link>
<Link
to="/projects/$projectId/cert-manager/certificate-templates"
params={{
projectId: currentWorkspace.id
}}
>
{({ isActive }) => (
<MenuItem isSelected={isActive}> Certificate Templates</MenuItem>
)}
</Link>
<Link
to="/projects/$projectId/cert-manager/certificates"
params={{
projectId: currentWorkspace.id
}}
>
{({ isActive }) => <MenuItem isSelected={isActive}> Certificates</MenuItem>}
</Link>
<Link
to="/projects/$projectId/cert-manager/certificate-authorities"
params={{
projectId: currentWorkspace.id
}}
>
{({ isActive }) => (
<MenuItem isSelected={isActive}> Certificates Authorities</MenuItem>
)}
</Link>
<Link
to="/projects/$projectId/cert-manager/alerting"
params={{
projectId: currentWorkspace.id
}}
>
{({ isActive }) => <MenuItem isSelected={isActive}>Alerting</MenuItem>}
</Link>
{
// <Link
// to="/projects/$projectId/cert-manager/settings"
// params={{
// projectId: currentWorkspace.id
// }}
// >
// {({ isActive }) => <MenuItem isSelected={isActive}>Settings</MenuItem>}
// </Link>
}
</Menu>
</div>
</nav>
</motion.div>
<div className="flex-1 overflow-y-auto overflow-x-hidden bg-bunker-800 p-4 pt-8">
{assumedPrivilegeDetails && <AssumePrivilegeModeBanner />}
<Outlet />
</div>
</div>
@@ -1,53 +0,0 @@
import { Link, Outlet } from "@tanstack/react-router";
import { motion } from "framer-motion";
import { Menu, MenuItem } from "@app/components/v2";
import { useWorkspace } from "@app/context";
export const ProjectGeneralLayout = () => {
const { currentWorkspace } = useWorkspace();
return (
<div className="dark hidden h-full w-full flex-col overflow-x-hidden md:flex">
<div className="flex flex-grow flex-col overflow-y-hidden md:flex-row">
<motion.div
key="menu-project-items"
initial={{ x: -150 }}
animate={{ x: 0 }}
exit={{ x: -150 }}
transition={{ duration: 0.2 }}
className="dark w-full border-r border-mineshaft-600 bg-gradient-to-tr from-mineshaft-700 via-mineshaft-800 to-mineshaft-900 md:w-60"
>
<nav className="items-between flex h-full flex-col overflow-y-auto dark:[color-scheme:dark]">
<div className="border-b border-mineshaft-600 px-4 py-3.5 text-lg text-white">
Project Overview
</div>
<div className="flex-1">
<Menu>
<Link
to="/projects/$projectId/access-management"
params={{
projectId: currentWorkspace.id
}}
>
{({ isActive }) => <MenuItem isSelected={isActive}>Access Control</MenuItem>}
</Link>
<Link
to="/projects/$projectId/settings"
params={{
projectId: currentWorkspace.id
}}
>
{({ isActive }) => <MenuItem isSelected={isActive}>Project Settings</MenuItem>}
</Link>
</Menu>
</div>
</nav>
</motion.div>
<div className="flex-1 overflow-y-auto overflow-x-hidden bg-bunker-800 p-4 pt-8">
<Outlet />
</div>
</div>
</div>
);
};
@@ -1 +0,0 @@
export { ProjectGeneralLayout } from "./ProjectGeneralLayout";
@@ -1,327 +0,0 @@
import { useTranslation } from "react-i18next";
import { faDotCircle, faMobile, faWindowMaximize } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { Link, Outlet, useLocation } from "@tanstack/react-router";
import { motion } from "framer-motion";
import { twMerge } from "tailwind-merge";
import { ShouldWrap } from "@app/components/utilities/ShouldWrapComponent";
import {
Divider,
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
Lottie,
Menu,
MenuItem,
Tooltip
} from "@app/components/v2";
import { useProjectPermission, useWorkspace } from "@app/context";
import { getCurrentProductFromUrl } from "@app/helpers/project";
import { useLocalStorageState } from "@app/hooks";
import { ProjectType } from "@app/hooks/api/workspace/types";
import { AssumePrivilegeModeBanner } from "./components/AssumePrivilegeModeBanner";
enum SidebarStyle {
Expanded = "expanded",
Collapsed = "collapsed",
ExpandOnHover = "expand-on-hover"
}
const MIN_SIDEBAR_SIZE = "55px";
const MAX_SIDEBAR_SIZE = "220px";
// This is a generic layout shared by all types of projects.
// If the product layout differs significantly, create a new layout as needed.
export const ProjectLayout = () => {
const location = useLocation();
const { currentWorkspace } = useWorkspace();
const [sidebarStyle, setSidebarStyle] = useLocalStorageState(
"project-sidebar-style",
SidebarStyle.ExpandOnHover
);
const { t } = useTranslation();
const { assumedPrivilegeDetails } = useProjectPermission();
const minSidebarWidth =
sidebarStyle === SidebarStyle.Expanded ? MAX_SIDEBAR_SIZE : MIN_SIDEBAR_SIZE;
const maxSidebarWidth =
sidebarStyle === SidebarStyle.Collapsed ? MIN_SIDEBAR_SIZE : MAX_SIDEBAR_SIZE;
const currentProductType = getCurrentProductFromUrl(location.pathname);
const isSecretManager = currentProductType === ProjectType.SecretManager;
const isPki = currentProductType === ProjectType.CertificateManager;
const isKms = currentProductType === ProjectType.KMS;
const isSsh = currentProductType === ProjectType.SSH;
const isSecretScanning = currentProductType === ProjectType.SecretScanning;
return (
<>
<div
className="dark relative hidden w-full flex-col overflow-x-hidden md:flex"
style={{
height: "calc(100vh - 3rem)",
paddingLeft: !(sidebarStyle === SidebarStyle.Expanded) ? MIN_SIDEBAR_SIZE : "0px"
}}
>
<div className="flex flex-grow flex-col overflow-y-auto overflow-x-hidden md:flex-row">
<motion.div
key="menu-project-items"
initial={{ x: -150 }}
animate={{ x: 0 }}
exit={{ x: -150 }}
transition={{ duration: 0.2 }}
style={{ width: minSidebarWidth, height: "calc(100vh - 3rem)" }}
whileHover={{
width: maxSidebarWidth
}}
className={twMerge(
"dark group z-10 w-full overflow-hidden border-r border-mineshaft-600 bg-gradient-to-tr from-mineshaft-700 via-mineshaft-800 to-mineshaft-900 md:w-60",
!(sidebarStyle === SidebarStyle.Expanded) && "absolute bottom-0 left-0"
)}
>
<nav className="items-between flex h-full flex-col justify-between">
<Menu>
<ShouldWrap
wrapper={Tooltip}
isWrapped={sidebarStyle === SidebarStyle.Collapsed}
content="Secret Manager"
position="right"
>
<Link
to="/projects/$projectId/secret-manager/overview"
params={{ projectId: currentWorkspace.id }}
>
<MenuItem
className="relative flex items-center gap-2 overflow-hidden rounded-none"
isSelected={isSecretManager}
leftIcon={<Lottie className="inline-block h-6 w-6 shrink-0" icon="vault" />}
>
{isSecretManager && (
<div className="absolute left-0 top-0 h-full w-0.5 bg-primary" />
)}
Secret Manager
</MenuItem>
</Link>
</ShouldWrap>
<ShouldWrap
wrapper={Tooltip}
isWrapped={sidebarStyle === SidebarStyle.Collapsed}
content="PKI Manager"
position="right"
>
<Link
to="/projects/$projectId/cert-manager/subscribers"
params={{ projectId: currentWorkspace.id }}
>
<MenuItem
className="relative flex items-center gap-2 overflow-hidden rounded-none"
isSelected={isPki}
leftIcon={<Lottie className="inline-block h-6 w-6 shrink-0" icon="note" />}
>
{isPki && <div className="absolute left-0 top-0 h-full w-0.5 bg-primary" />}
PKI Manager
</MenuItem>
</Link>
</ShouldWrap>
<ShouldWrap
wrapper={Tooltip}
isWrapped={sidebarStyle === SidebarStyle.Collapsed}
content="KMS"
position="right"
>
<Link
to="/projects/$projectId/kms/overview"
params={{ projectId: currentWorkspace.id }}
>
<MenuItem
className="relative flex items-center gap-2 overflow-hidden rounded-none"
isSelected={isKms}
leftIcon={<Lottie className="inline-block h-6 w-6 shrink-0" icon="unlock" />}
>
{isKms && <div className="absolute left-0 top-0 h-full w-0.5 bg-primary" />}
KMS
</MenuItem>
</Link>
</ShouldWrap>
<ShouldWrap
wrapper={Tooltip}
isWrapped={sidebarStyle === SidebarStyle.Collapsed}
content="SSH"
position="right"
>
<Link
to="/projects/$projectId/ssh/overview"
params={{ projectId: currentWorkspace.id }}
>
<MenuItem
className="relative flex items-center gap-2 overflow-hidden rounded-none"
isSelected={isSsh}
leftIcon={
<Lottie className="inline-block h-6 w-6 shrink-0" icon="terminal" />
}
>
{isSsh && <div className="absolute left-0 top-0 h-full w-0.5 bg-primary" />}
SSH
</MenuItem>
</Link>
</ShouldWrap>
<ShouldWrap
wrapper={Tooltip}
isWrapped={sidebarStyle === SidebarStyle.Collapsed}
content="Secret Scanning"
position="right"
>
<Link
to="/projects/$projectId/secret-scanning/data-sources"
params={{ projectId: currentWorkspace.id }}
>
<MenuItem
className="relative flex items-center gap-2 overflow-hidden rounded-none"
isSelected={isSecretScanning}
leftIcon={
<Lottie className="inline-block h-6 w-6 shrink-0" icon="secret-scan" />
}
>
{isSecretScanning && (
<div className="absolute left-0 top-0 h-full w-0.5 bg-primary" />
)}
Secret Scanning
</MenuItem>
</Link>
</ShouldWrap>
</Menu>
<Divider />
<Menu>
<ShouldWrap
wrapper={Tooltip}
isWrapped={sidebarStyle === SidebarStyle.Collapsed}
content="Access Control"
position="right"
>
<Link
to="/projects/$projectId/access-management"
params={{ projectId: currentWorkspace.id }}
>
{({ isActive }) => (
<MenuItem
className="relative flex items-center gap-2 overflow-hidden rounded-none"
isSelected={isActive}
leftIcon={
<Lottie className="inline-block h-6 w-6 shrink-0" icon="groups" />
}
>
{isActive && (
<div className="absolute left-0 top-0 h-full w-0.5 bg-primary" />
)}
Access Control
</MenuItem>
)}
</Link>
</ShouldWrap>
<ShouldWrap
wrapper={Tooltip}
isWrapped={sidebarStyle === SidebarStyle.Collapsed}
content="Project Settings"
position="right"
>
<Link
to="/projects/$projectId/settings"
params={{ projectId: currentWorkspace.id }}
>
{({ isActive }) => (
<MenuItem
className="relative flex items-center gap-2 overflow-hidden rounded-none"
isSelected={isActive}
leftIcon={
<Lottie className="inline-block h-6 w-6 shrink-0" icon="settings-cog" />
}
>
{isActive && (
<div className="absolute left-0 top-0 h-full w-0.5 bg-primary" />
)}
Project Settings
</MenuItem>
)}
</Link>
</ShouldWrap>
</Menu>
<div className="flex-grow" />
<Menu>
<DropdownMenu>
<ShouldWrap
wrapper={Tooltip}
isWrapped={sidebarStyle === SidebarStyle.Collapsed}
content="Sidebar Control"
position="right"
>
<DropdownMenuTrigger className="w-full">
<MenuItem
className="relative flex items-center gap-2 overflow-hidden text-sm text-mineshaft-400 hover:text-mineshaft-300"
leftIcon={
<FontAwesomeIcon
className="mx-1 inline-block shrink-0"
icon={faWindowMaximize}
flip="vertical"
/>
}
>
Sidebar Control
</MenuItem>
</DropdownMenuTrigger>
</ShouldWrap>
<DropdownMenuContent>
<DropdownMenuItem
iconPos="right"
icon={
sidebarStyle === SidebarStyle.Expanded && (
<FontAwesomeIcon icon={faDotCircle} size="sm" />
)
}
onClick={() => setSidebarStyle(SidebarStyle.Expanded)}
>
Expanded
</DropdownMenuItem>
<DropdownMenuItem
iconPos="right"
icon={
sidebarStyle === SidebarStyle.Collapsed && (
<FontAwesomeIcon icon={faDotCircle} size="sm" />
)
}
onClick={() => setSidebarStyle(SidebarStyle.Collapsed)}
>
Collapsed
</DropdownMenuItem>
<DropdownMenuItem
iconPos="right"
icon={
sidebarStyle === SidebarStyle.ExpandOnHover && (
<FontAwesomeIcon icon={faDotCircle} size="sm" />
)
}
onClick={() => setSidebarStyle(SidebarStyle.ExpandOnHover)}
>
Expand on hover
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</Menu>
</nav>
</motion.div>
<div className="flex-1 overflow-y-auto overflow-x-hidden bg-bunker-800">
{assumedPrivilegeDetails && <AssumePrivilegeModeBanner />}
<Outlet />
</div>
</div>
</div>
<div className="z-[200] flex h-screen w-screen flex-col items-center justify-center bg-bunker-800 md:hidden">
<FontAwesomeIcon icon={faMobile} className="mb-8 text-7xl text-gray-300" />
<p className="max-w-sm px-6 text-center text-lg text-gray-200">
{` ${t("common.no-mobile")} `}
</p>
</div>
</>
);
};
@@ -3,10 +3,9 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { Button } from "@app/components/v2";
import { useProjectPermission, useWorkspace } from "@app/context";
import { getCurrentProductFromUrl, getProjectHomePage } from "@app/helpers/project";
import { getProjectHomePage } from "@app/helpers/project";
import { useRemoveAssumeProjectPrivilege } from "@app/hooks/api";
import { ActorType } from "@app/hooks/api/auditLogs/enums";
import { ProjectType } from "@app/hooks/api/workspace/types";
export const AssumePrivilegeModeBanner = () => {
const { currentWorkspace } = useWorkspace();
@@ -37,10 +36,7 @@ export const AssumePrivilegeModeBanner = () => {
},
{
onSuccess: () => {
const url = getProjectHomePage(
getCurrentProductFromUrl(window.location.href) || ProjectType.SecretManager
);
const url = getProjectHomePage(currentWorkspace.type);
window.location.href = url.replace("$projectId", currentWorkspace.id);
}
}
@@ -1,121 +0,0 @@
import { ComponentPropsWithRef, ElementType, useRef } from "react";
import { DotLottie, DotLottieReact } from "@lottiefiles/dotlottie-react";
import { twMerge } from "tailwind-merge";
import { MenuItemProps } from "@app/components/v2";
export const MenuIconButton = <T extends ElementType = "button">({
children,
icon,
className,
isDisabled,
isSelected,
as: Item = "div",
description,
// wrapping in forward ref with generic component causes the loss of ts definitions on props
inputRef,
lottieIconMode = "forward",
...props
}: MenuItemProps<T> &
ComponentPropsWithRef<T> & { lottieIconMode?: "reverse" | "forward" }): JSX.Element => {
const iconRef = useRef<DotLottie | null>(null);
return (
<div className="px-1">
<Item
type="button"
role="menuitem"
className={twMerge(
"group relative my-1 flex w-full cursor-pointer flex-col items-center justify-center rounded p-2 font-inter text-sm text-bunker-100 transition-all duration-150 hover:bg-mineshaft-700",
isSelected && "bg-bunker-8g0 hover:bg-mineshaft-500",
isDisabled && "cursor-not-allowed hover:bg-transparent",
className
)}
onMouseEnter={() => iconRef.current?.play()}
onMouseLeave={() => iconRef.current?.stop()}
ref={inputRef}
{...props}
>
<div
className={`${isSelected ? "opacity-100" : "opacity-0"} absolute left-0 h-full w-0.5 rounded-bl rounded-tl bg-primary transition-all duration-150`}
/>
{icon && (
<div className="my-auto mb-2 h-6 w-6">
<DotLottieReact
dotLottieRefCallback={(el) => {
iconRef.current = el;
}}
src={`/lotties/${icon}.json`}
loop
className="h-full w-full"
mode={lottieIconMode}
/>
</div>
)}
<div
className="flex-grow justify-center break-words text-center"
style={{ fontSize: "10px" }}
>
{children}
</div>
</Item>
</div>
);
};
// export const MenuIconButton = <T extends ElementType = "button">({
// children,
// icon,
// className,
// isDisabled,
// isSelected,
// as: Item = "div",
// description,
// // wrapping in forward ref with generic component causes the loss of ts definitions on props
// inputRef,
// lottieIconMode = "forward",
// ...props
// }: MenuItemProps<T> &
// ComponentPropsWithRef<T> & { lottieIconMode?: "reverse" | "forward" }): JSX.Element => {
// const iconRef = useRef<DotLottie | null>(null);
// return (
// <div className="px-1">
// <Item
// type="button"
// role="menuitem"
// className={twMerge(
// "group relative my-1 flex w-full cursor-pointer flex-col items-center justify-center rounded p-2 font-inter text-sm text-bunker-100 transition-all duration-150 hover:bg-mineshaft-700",
// isSelected && "bg-mineshaft-600 hover:bg-mineshaft-700",
// isDisabled && "cursor-not-allowed hover:bg-transparent",
// className
// )}
// onMouseEnter={() => iconRef.current?.play()}
// onMouseLeave={() => iconRef.current?.stop()}
// ref={inputRef}
// {...props}
// >
// <div
// className={`${"opacity-0"} absolute left-0 h-full w-1 bg-primary transition-all duration-150`}
// />
// {icon && (
// <div className="my-auto mb-2 h-6 w-6">
// <DotLottieReact
// dotLottieRefCallback={(el) => {
// iconRef.current = el;
// }}
// src={`/lotties/${icon}.json`}
// loop
// className="h-full w-full"
// mode={lottieIconMode}
// />
// </div>
// )}
// <div
// className="flex-grow justify-center break-words text-center"
// style={{ fontSize: "10px" }}
// >
// {children}
// </div>
// </Item>
// </div>
// );
// };
@@ -1,65 +0,0 @@
import { ComponentPropsWithRef, ElementType, useRef } from "react";
import { DotLottie, DotLottieReact } from "@lottiefiles/dotlottie-react";
import { twMerge } from "tailwind-merge";
import { MenuItemProps } from "@app/components/v2";
export const MenuIconButton = <T extends ElementType = "button">({
children,
icon,
className,
isDisabled,
isSelected,
as: Item = "div",
description,
// wrapping in forward ref with generic component causes the loss of ts definitions on props
inputRef,
lottieIconMode = "forward",
...props
}: MenuItemProps<T> &
ComponentPropsWithRef<T> & { lottieIconMode?: "reverse" | "forward" }): JSX.Element => {
const iconRef = useRef<DotLottie | null>(null);
return (
<div>
<Item
type="button"
role="menuitem"
className={twMerge(
"group relative flex w-full cursor-pointer flex-col items-center justify-center p-2 font-inter text-sm text-bunker-100 transition-all duration-150 hover:bg-mineshaft-700",
isSelected && "rounded-none bg-bunker-800 hover:bg-mineshaft-600",
isDisabled && "cursor-not-allowed hover:bg-transparent",
className
)}
onMouseEnter={() => iconRef.current?.play()}
onMouseLeave={() => iconRef.current?.stop()}
ref={inputRef}
{...props}
>
<div
className={`${
isSelected ? "opacity-100" : "opacity-0"
} absolute left-0 h-full w-0.5 bg-primary transition-all duration-150`}
/>
{icon && (
<div className="my-auto mb-2 h-6 w-6">
<DotLottieReact
dotLottieRefCallback={(el) => {
iconRef.current = el;
}}
src={`/lotties/${icon}.json`}
loop
className="h-full w-full"
mode={lottieIconMode}
/>
</div>
)}
<div
className="flex-grow justify-center break-words text-center"
style={{ fontSize: "10px" }}
>
{children}
</div>
</Item>
</div>
);
};
@@ -1 +0,0 @@
export { MenuIconButton } from "./MenuIconButton";
@@ -31,14 +31,13 @@ import {
useSubscription,
useWorkspace
} from "@app/context";
import { getCurrentProductFromUrl, getProjectHomePage } from "@app/helpers/project";
import { getProjectHomePage } from "@app/helpers/project";
import { usePopUp } from "@app/hooks";
import { useGetUserWorkspaces } from "@app/hooks/api";
import { useUpdateUserProjectFavorites } from "@app/hooks/api/users/mutation";
import { useGetUserProjectFavorites } from "@app/hooks/api/users/queries";
import { Workspace } from "@app/hooks/api/workspace/types";
// TODO(pta): add search to project select
export const ProjectSelect = () => {
const [searchProject, setSearchProject] = useState("");
const { currentWorkspace } = useWorkspace();
@@ -102,9 +101,7 @@ export const ProjectSelect = () => {
<div className="-mr-2 flex w-full items-center gap-1">
<DropdownMenu modal={false}>
<Link
to={getProjectHomePage(
getCurrentProductFromUrl(window.location.href) || currentWorkspace.defaultProduct
)}
to={getProjectHomePage(currentWorkspace.type)}
params={{
projectId: currentWorkspace.id
}}
@@ -113,7 +110,7 @@ export const ProjectSelect = () => {
<div>
<FontAwesomeIcon icon={faTable} className="text-xs text-bunker-300" />
</div>
<Tooltip content={currentWorkspace.name} className="max-w-96">
<Tooltip content={currentWorkspace.name} className="max-w-96 break-words">
<div className="max-w-44 overflow-hidden text-ellipsis whitespace-nowrap">
{currentWorkspace?.name}
</div>
@@ -161,7 +158,7 @@ export const ProjectSelect = () => {
// to reproduce change this back to router.push and switch between two projects with different env count
// look into this on dashboard revamp
const url = linkOptions({
to: getProjectHomePage(workspace.defaultProduct),
to: getProjectHomePage(workspace.type),
params: {
projectId: workspace.id
}
@@ -1,179 +0,0 @@
import {
faAngleDown,
faArrowLeft,
faArrowUpRightFromSquare,
faCheck
} from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { Link, useNavigate } from "@tanstack/react-router";
import {
Button,
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger
} from "@app/components/v2";
import { useOrganization, useUser } from "@app/context";
import { useGetOrganizations, useLogoutUser } from "@app/hooks/api";
import { AuthMethod } from "@app/hooks/api/users/types";
type Prop = {
onChangeOrg: (orgId: string) => void;
};
export const SidebarHeader = ({ onChangeOrg }: Prop) => {
const { currentOrg } = useOrganization();
const { user } = useUser();
const navigate = useNavigate();
const { data: orgs } = useGetOrganizations();
const logout = useLogoutUser();
const logOutUser = async () => {
try {
console.log("Logging out...");
await logout.mutateAsync();
navigate({ to: "/login" });
} catch (error) {
console.error(error);
}
};
return (
<div className="flex h-12 cursor-default items-center px-3 pt-6">
<Link to="/organization/projects">
<div className="pl-1 pr-2 text-mineshaft-400 duration-200 hover:text-mineshaft-100">
<FontAwesomeIcon icon={faArrowLeft} />
</div>
</Link>
<DropdownMenu>
<DropdownMenuTrigger asChild className="max-w-[160px] data-[state=open]:bg-mineshaft-600">
<div className="mr-auto flex items-center rounded-md py-1.5 pl-1.5 pr-2 hover:bg-mineshaft-600">
<div className="flex h-5 w-5 min-w-[20px] items-center justify-center rounded-md bg-primary text-sm">
{currentOrg?.name.charAt(0)}
</div>
<div
className="overflow-hidden truncate text-ellipsis pl-2 text-sm text-mineshaft-100"
style={{ maxWidth: "140px" }}
>
{currentOrg?.name}
</div>
<FontAwesomeIcon icon={faAngleDown} className="pl-1 pt-1 text-xs text-mineshaft-300" />
</div>
</DropdownMenuTrigger>
<DropdownMenuContent align="start" className="p-1">
<div className="px-2 py-1 text-xs text-mineshaft-400">{user?.username}</div>
{orgs?.map((org) => {
return (
<DropdownMenuItem key={org.id}>
<Button
onClick={async () => {
if (currentOrg?.id === org.id) return;
if (org.authEnforced) {
// org has an org-level auth method enabled (e.g. SAML)
// -> logout + redirect to SAML SSO
await logout.mutateAsync();
if (org.orgAuthMethod === AuthMethod.OIDC) {
window.open(`/api/v1/sso/oidc/login?orgSlug=${org.slug}`);
} else {
window.open(`/api/v1/sso/redirect/saml2/organizations/${org.slug}`);
}
window.close();
return;
}
onChangeOrg(org?.id);
}}
variant="plain"
colorSchema="secondary"
size="xs"
className="flex w-full items-center justify-start p-0 font-normal"
leftIcon={
currentOrg?.id === org.id && (
<FontAwesomeIcon icon={faCheck} className="mr-3 text-primary" />
)
}
>
<div className="flex w-full max-w-[150px] items-center justify-between truncate">
{org.name}
</div>
</Button>
</DropdownMenuItem>
);
})}
<div className="mt-1 h-1 border-t border-mineshaft-600" />
<button type="button" onClick={logOutUser} className="w-full">
<DropdownMenuItem>Log Out</DropdownMenuItem>
</button>
</DropdownMenuContent>
</DropdownMenu>
<DropdownMenu>
<DropdownMenuTrigger
asChild
className="p-1 hover:bg-primary-400 hover:text-black data-[state=open]:bg-primary-400 data-[state=open]:text-black"
>
<div
className="child flex items-center justify-center rounded-full bg-mineshaft pr-1 text-mineshaft-300 hover:bg-mineshaft-500"
style={{ fontSize: "11px", width: "26px", height: "26px" }}
>
{user?.firstName?.charAt(0)}
{user?.lastName && user?.lastName?.charAt(0)}
</div>
</DropdownMenuTrigger>
<DropdownMenuContent align="start" className="p-1">
<div className="px-2 py-1 text-xs text-mineshaft-400">{user?.username}</div>
<Link to="/personal-settings">
<DropdownMenuItem>Personal Settings</DropdownMenuItem>
</Link>
<a
href="https://infisical.com/docs/documentation/getting-started/introduction"
target="_blank"
rel="noopener noreferrer"
className="mt-3 w-full text-sm font-normal leading-[1.2rem] text-mineshaft-300 hover:text-mineshaft-100"
>
<DropdownMenuItem>
Documentation
<FontAwesomeIcon
icon={faArrowUpRightFromSquare}
className="mb-[0.06rem] pl-1.5 text-xxs"
/>
</DropdownMenuItem>
</a>
<a
href="https://infisical.com/slack"
target="_blank"
rel="noopener noreferrer"
className="mt-3 w-full text-sm font-normal leading-[1.2rem] text-mineshaft-300 hover:text-mineshaft-100"
>
<DropdownMenuItem>
Join Slack Community
<FontAwesomeIcon
icon={faArrowUpRightFromSquare}
className="mb-[0.06rem] pl-1.5 text-xxs"
/>
</DropdownMenuItem>
</a>
{user?.superAdmin && (
<Link to="/admin">
<DropdownMenuItem className="mt-1 border-t border-mineshaft-600">
Server Admin Console
</DropdownMenuItem>
</Link>
)}
<Link to="/organization/admin">
<DropdownMenuItem className="mt-1 border-t border-mineshaft-600">
Organization Admin Console
</DropdownMenuItem>
</Link>
<div className="mt-1 h-1 border-t border-mineshaft-600" />
<button type="button" onClick={logOutUser} className="w-full">
<DropdownMenuItem>Log Out</DropdownMenuItem>
</button>
</DropdownMenuContent>
</DropdownMenu>
</div>
);
};
@@ -1 +0,0 @@
export { SidebarHeader } from "./SidebarHeader";
@@ -1 +0,0 @@
export { ProjectLayout } from "./ProjectLayout";
@@ -1,19 +1,31 @@
import { useTranslation } from "react-i18next";
import { faMobile } from "@fortawesome/free-solid-svg-icons";
import {
faArrowsSpin,
faCheckToSlot,
faCog,
faHome,
faMobile,
faPuzzlePiece,
faUsers,
faVault
} from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { Link, Outlet } from "@tanstack/react-router";
import { motion } from "framer-motion";
import { Badge, Menu, MenuItem } from "@app/components/v2";
import { useWorkspace } from "@app/context";
import { Badge, Lottie, Menu, MenuGroup, MenuItem } from "@app/components/v2";
import { useProjectPermission, useWorkspace } from "@app/context";
import {
useGetAccessRequestsCount,
useGetSecretApprovalRequestCount,
useGetSecretRotations
} from "@app/hooks/api";
import { AssumePrivilegeModeBanner } from "../ProjectLayout/components/AssumePrivilegeModeBanner";
export const SecretManagerLayout = () => {
const { currentWorkspace } = useWorkspace();
const { assumedPrivilegeDetails } = useProjectPermission();
const { t } = useTranslation();
const workspaceId = currentWorkspace?.id || "";
@@ -50,73 +62,150 @@ export const SecretManagerLayout = () => {
className="dark w-full border-r border-mineshaft-600 bg-gradient-to-tr from-mineshaft-700 via-mineshaft-800 to-mineshaft-900 md:w-60"
>
<nav className="items-between flex h-full flex-col overflow-y-auto dark:[color-scheme:dark]">
<div className="border-b border-mineshaft-600 px-4 py-3.5 text-lg text-white">
<div className="flex items-center gap-3 border-b border-mineshaft-600 px-4 py-3.5 text-lg text-white">
<Lottie className="inline-block h-5 w-5 shrink-0" icon="vault" />
Secrets Manager
</div>
<div className="flex-1">
<Menu>
<Link
to="/projects/$projectId/secret-manager/overview"
params={{
projectId: currentWorkspace.id
}}
>
{({ isActive }) => (
<MenuItem isSelected={isActive}>{t("nav.menu.secrets")}</MenuItem>
)}
</Link>
<Link
to="/projects/$projectId/secret-manager/integrations"
params={{
projectId: currentWorkspace.id
}}
>
{({ isActive }) => (
<MenuItem isSelected={isActive}>{t("nav.menu.integrations")}</MenuItem>
)}
</Link>
{Boolean(secretRotations?.length) && (
<MenuGroup title="Resources">
<Link
to="/projects/$projectId/secret-manager/secret-rotation"
to="/projects/secret-management/$projectId/overview"
params={{
projectId: currentWorkspace.id
}}
>
{({ isActive }) => <MenuItem isSelected={isActive}>Secret Rotation</MenuItem>}
{({ isActive }) => (
<MenuItem isSelected={isActive}>
<div className="mx-1 flex gap-2">
<div className="w-6">
<FontAwesomeIcon icon={faVault} />
</div>
Secrets
</div>
</MenuItem>
)}
</Link>
)}
<Link
to="/projects/$projectId/secret-manager/approval"
params={{
projectId: currentWorkspace.id
}}
>
{({ isActive }) => (
<MenuItem isSelected={isActive}>
Approvals
{Boolean(
secretApprovalReqCount?.open || accessApprovalRequestCount?.pendingCount
) && (
<Badge variant="primary" className="ml-1.5">
{pendingRequestsCount}
</Badge>
<Link
to="/projects/secret-management/$projectId/integrations"
params={{
projectId: currentWorkspace.id
}}
>
{({ isActive }) => (
<MenuItem isSelected={isActive}>
<div className="mx-1 flex gap-2">
<div className="w-6">
<FontAwesomeIcon icon={faPuzzlePiece} />
</div>
Integrations
</div>
</MenuItem>
)}
</Link>
{Boolean(secretRotations?.length) && (
<Link
to="/projects/secret-management/$projectId/secret-rotation"
params={{
projectId: currentWorkspace.id
}}
>
{({ isActive }) => (
<MenuItem isSelected={isActive}>
<div className="mx-1 flex gap-2">
<div className="w-6">
<FontAwesomeIcon icon={faArrowsSpin} />
</div>
Secret Rotations
</div>
</MenuItem>
)}
</MenuItem>
</Link>
)}
</Link>
<Link
to="/projects/$projectId/secret-manager/settings"
params={{
projectId: currentWorkspace.id
}}
>
{({ isActive }) => <MenuItem isSelected={isActive}>Settings</MenuItem>}
<Link
to="/projects/secret-management/$projectId/approval"
params={{
projectId: currentWorkspace.id
}}
>
{({ isActive }) => (
<MenuItem isSelected={isActive}>
<div className="mx-1 flex gap-2">
<div className="w-6">
<FontAwesomeIcon icon={faCheckToSlot} />
</div>
Approvals
{Boolean(
secretApprovalReqCount?.open ||
accessApprovalRequestCount?.pendingCount
) && (
<Badge variant="primary" className="ml-1.5">
{pendingRequestsCount}
</Badge>
)}
</div>
</MenuItem>
)}
</Link>
</MenuGroup>
<MenuGroup title="Others">
<Link
to="/projects/secret-management/$projectId/access-management"
params={{
projectId: currentWorkspace.id
}}
>
{({ isActive }) => (
<MenuItem isSelected={isActive}>
<div className="mx-1 flex gap-2">
<div className="w-6">
<FontAwesomeIcon icon={faUsers} />
</div>
Access Management
</div>
</MenuItem>
)}
</Link>
<Link
to="/projects/secret-management/$projectId/settings"
params={{
projectId: currentWorkspace.id
}}
>
{({ isActive }) => (
<MenuItem isSelected={isActive}>
<div className="mx-1 flex gap-2">
<div className="w-6">
<FontAwesomeIcon icon={faCog} />
</div>
Settings
</div>
</MenuItem>
)}
</Link>
</MenuGroup>
</Menu>
</div>
<div>
<Menu>
<Link to="/organization/projects">
<MenuItem
className="relative flex items-center gap-2 overflow-hidden text-sm text-mineshaft-400 hover:text-mineshaft-300"
leftIcon={
<div className="w-6">
<FontAwesomeIcon className="mx-1 inline-block shrink-0" icon={faHome} />
</div>
}
>
Organization Home
</MenuItem>
</Link>
</Menu>
</div>
</nav>
</motion.div>
<div className="flex-1 overflow-y-auto overflow-x-hidden bg-bunker-800 p-4 pt-8">
{assumedPrivilegeDetails && <AssumePrivilegeModeBanner />}
<Outlet />
</div>
</div>
@@ -1,11 +1,22 @@
import {
faCog,
faDatabase,
faHome,
faMagnifyingGlass,
faUsers
} from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { Link, Outlet } from "@tanstack/react-router";
import { motion } from "framer-motion";
import { Menu, MenuItem } from "@app/components/v2";
import { useWorkspace } from "@app/context";
import { Lottie, Menu, MenuGroup, MenuItem } from "@app/components/v2";
import { useProjectPermission, useWorkspace } from "@app/context";
import { AssumePrivilegeModeBanner } from "../ProjectLayout/components/AssumePrivilegeModeBanner";
export const SecretScanningLayout = () => {
const { currentWorkspace } = useWorkspace();
const { assumedPrivilegeDetails } = useProjectPermission();
return (
<div className="dark hidden h-full w-full flex-col overflow-x-hidden md:flex">
@@ -19,40 +30,106 @@ export const SecretScanningLayout = () => {
className="dark w-full border-r border-mineshaft-600 bg-gradient-to-tr from-mineshaft-700 via-mineshaft-800 to-mineshaft-900 md:w-60"
>
<nav className="items-between flex h-full flex-col overflow-y-auto dark:[color-scheme:dark]">
<div className="border-b border-mineshaft-600 px-4 py-3.5 text-lg text-white">
<div className="flex items-center gap-3 border-b border-mineshaft-600 px-4 py-3.5 text-lg text-white">
<Lottie className="inline-block h-5 w-5 shrink-0" icon="secret-scan" />
Secret Scanning
</div>
<div className="flex-1">
<Menu>
<Link
to="/projects/$projectId/secret-scanning/data-sources"
params={{
projectId: currentWorkspace.id
}}
>
{({ isActive }) => <MenuItem isSelected={isActive}> Data Sources</MenuItem>}
</Link>
<Link
to="/projects/$projectId/secret-scanning/findings"
params={{
projectId: currentWorkspace.id
}}
>
{({ isActive }) => <MenuItem isSelected={isActive}>Findings</MenuItem>}
</Link>
<Link
to="/projects/$projectId/secret-scanning/settings"
params={{
projectId: currentWorkspace.id
}}
>
{({ isActive }) => <MenuItem isSelected={isActive}>Settings</MenuItem>}
<MenuGroup title="Resources">
<Link
to="/projects/secret-scanning/$projectId/data-sources"
params={{
projectId: currentWorkspace.id
}}
>
{({ isActive }) => (
<MenuItem isSelected={isActive}>
<div className="mx-1 flex gap-2">
<div className="w-6">
<FontAwesomeIcon icon={faDatabase} />
</div>
Data Sources
</div>
</MenuItem>
)}
</Link>
<Link
to="/projects/secret-scanning/$projectId/findings"
params={{
projectId: currentWorkspace.id
}}
>
{({ isActive }) => (
<MenuItem isSelected={isActive}>
<div className="mx-1 flex gap-2">
<div className="w-6">
<FontAwesomeIcon icon={faMagnifyingGlass} />
</div>
Findings
</div>
</MenuItem>
)}
</Link>
</MenuGroup>
<MenuGroup title="Others">
<Link
to="/projects/secret-scanning/$projectId/access-management"
params={{
projectId: currentWorkspace.id
}}
>
{({ isActive }) => (
<MenuItem isSelected={isActive}>
<div className="mx-1 flex gap-2">
<div className="w-6">
<FontAwesomeIcon icon={faUsers} />
</div>
Access Management
</div>
</MenuItem>
)}
</Link>
<Link
to="/projects/secret-scanning/$projectId/settings"
params={{
projectId: currentWorkspace.id
}}
>
{({ isActive }) => (
<MenuItem isSelected={isActive}>
<div className="mx-1 flex gap-2">
<div className="w-6">
<FontAwesomeIcon icon={faCog} />
</div>
Settings
</div>
</MenuItem>
)}
</Link>
</MenuGroup>
</Menu>
</div>
<div>
<Menu>
<Link to="/organization/projects">
<MenuItem
className="relative flex items-center gap-2 overflow-hidden text-sm text-mineshaft-400 hover:text-mineshaft-300"
leftIcon={
<div className="w-6">
<FontAwesomeIcon className="mx-1 inline-block shrink-0" icon={faHome} />
</div>
}
>
Organization Home
</MenuItem>
</Link>
</Menu>
</div>
</nav>
</motion.div>
<div className="flex-1 overflow-y-auto overflow-x-hidden bg-bunker-800 p-4 pt-8">
{assumedPrivilegeDetails && <AssumePrivilegeModeBanner />}
<Outlet />
</div>
</div>
+114 -37
View File
@@ -1,12 +1,22 @@
import { faCog, faHome, faServer, faStamp, faUsers } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { Link, Outlet } from "@tanstack/react-router";
import { motion } from "framer-motion";
import { ProjectPermissionCan } from "@app/components/permissions";
import { Menu, MenuItem } from "@app/components/v2";
import { ProjectPermissionActions, ProjectPermissionSub, useWorkspace } from "@app/context";
import { Lottie, Menu, MenuGroup, MenuItem } from "@app/components/v2";
import {
ProjectPermissionActions,
ProjectPermissionSub,
useProjectPermission,
useWorkspace
} from "@app/context";
import { AssumePrivilegeModeBanner } from "../ProjectLayout/components/AssumePrivilegeModeBanner";
export const SshLayout = () => {
const { currentWorkspace } = useWorkspace();
const { assumedPrivilegeDetails } = useProjectPermission();
return (
<div className="dark hidden h-full w-full flex-col overflow-x-hidden md:flex">
@@ -20,49 +30,116 @@ export const SshLayout = () => {
className="dark w-full border-r border-mineshaft-600 bg-gradient-to-tr from-mineshaft-700 via-mineshaft-800 to-mineshaft-900 md:w-60"
>
<nav className="items-between flex h-full flex-col overflow-y-auto dark:[color-scheme:dark]">
<div className="border-b border-mineshaft-600 px-4 py-3.5 text-lg text-white">SSH</div>
<div className="flex items-center gap-3 border-b border-mineshaft-600 px-4 py-3.5 text-lg text-white">
<Lottie className="inline-block h-5 w-5 shrink-0" icon="terminal" />
SSH
</div>
<div className="flex-1">
<Menu>
<Link
to="/projects/$projectId/ssh/overview"
params={{
projectId: currentWorkspace.id
}}
>
{({ isActive }) => <MenuItem isSelected={isActive}>Hosts</MenuItem>}
</Link>
<ProjectPermissionCan
I={ProjectPermissionActions.Read}
a={ProjectPermissionSub.SshCertificateAuthorities}
>
{(isAllowed) =>
isAllowed && (
<Link
to="/projects/$projectId/ssh/cas"
params={{
projectId: currentWorkspace.id
}}
>
{({ isActive }) => (
<MenuItem isSelected={isActive}>Certificate Authorities</MenuItem>
)}
</Link>
)
}
</ProjectPermissionCan>
<Link
to="/projects/$projectId/ssh/settings"
params={{
projectId: currentWorkspace.id
}}
>
{({ isActive }) => <MenuItem isSelected={isActive}>Settings</MenuItem>}
<MenuGroup title="Resources">
<Link
to="/projects/ssh/$projectId/overview"
params={{
projectId: currentWorkspace.id
}}
>
{({ isActive }) => (
<MenuItem isSelected={isActive}>
<div className="mx-1 flex gap-2">
<div className="w-6">
<FontAwesomeIcon icon={faServer} />
</div>
Hosts
</div>
</MenuItem>
)}
</Link>
<ProjectPermissionCan
I={ProjectPermissionActions.Read}
a={ProjectPermissionSub.SshCertificateAuthorities}
>
{(isAllowed) =>
isAllowed && (
<Link
to="/projects/ssh/$projectId/cas"
params={{
projectId: currentWorkspace.id
}}
>
{({ isActive }) => (
<MenuItem isSelected={isActive}>
<div className="mx-1 flex gap-2">
<div className="w-6">
<FontAwesomeIcon icon={faStamp} />
</div>
Certificates Authority
</div>
</MenuItem>
)}
</Link>
)
}
</ProjectPermissionCan>
</MenuGroup>
<MenuGroup title="Others">
<Link
to="/projects/ssh/$projectId/access-management"
params={{
projectId: currentWorkspace.id
}}
>
{({ isActive }) => (
<MenuItem isSelected={isActive}>
<div className="mx-1 flex gap-2">
<div className="w-6">
<FontAwesomeIcon icon={faUsers} />
</div>
Access Management
</div>
</MenuItem>
)}
</Link>
<Link
to="/projects/ssh/$projectId/settings"
params={{
projectId: currentWorkspace.id
}}
>
{({ isActive }) => (
<MenuItem isSelected={isActive}>
<div className="mx-1 flex gap-2">
<div className="w-6">
<FontAwesomeIcon icon={faCog} />
</div>
Settings
</div>
</MenuItem>
)}
</Link>
</MenuGroup>
</Menu>
</div>
<div>
<Menu>
<Link to="/organization/projects">
<MenuItem
className="relative flex items-center gap-2 overflow-hidden text-sm text-mineshaft-400 hover:text-mineshaft-300"
leftIcon={
<div className="w-6">
<FontAwesomeIcon className="mx-1 inline-block shrink-0" icon={faHome} />
</div>
}
>
Organization Home
</MenuItem>
</Link>
</Menu>
</div>
</nav>
</motion.div>
<div className="flex-1 overflow-y-auto overflow-x-hidden bg-bunker-800 p-4 pt-8">
{assumedPrivilegeDetails && <AssumePrivilegeModeBanner />}
<Outlet />
</div>
</div>
@@ -82,7 +82,7 @@ export const PkiCollectionModal = ({ popUp, handlePopUpToggle }: Props) => {
});
navigate({
to: "/projects/$projectId/cert-manager/pki-collections/$collectionId",
to: "/projects/cert-management/$projectId/pki-collections/$collectionId",
params: {
projectId,
collectionId
@@ -21,7 +21,6 @@ import {
} from "@app/components/v2";
import { ProjectPermissionActions, ProjectPermissionSub, useWorkspace } from "@app/context";
import { useListWorkspacePkiCollections } from "@app/hooks/api";
import { ProjectType } from "@app/hooks/api/workspace/types";
import { UsePopUpState } from "@app/hooks/usePopUp";
type Props = {
@@ -61,7 +60,7 @@ export const PkiCollectionTable = ({ handlePopUpOpen }: Props) => {
key={`pki-collection-${pkiCollection.id}`}
onClick={() =>
navigate({
to: `/projects/$projectId/${ProjectType.CertificateManager}/pki-collections/$collectionId` as const,
to: "/projects/cert-management/$projectId/pki-collections/$collectionId",
params: {
projectId,
collectionId: pkiCollection.id
@@ -3,7 +3,7 @@ import { createFileRoute } from "@tanstack/react-router";
import { AlertingPage } from "./AlertingPage";
export const Route = createFileRoute(
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/cert-manager/_cert-manager-layout/alerting"
"/_authenticate/_inject-org-details/_org-layout/projects/cert-management/$projectId/_cert-manager-layout/alerting"
)({
component: AlertingPage,
beforeLoad: ({ context }) => {
@@ -18,7 +18,6 @@ import { ROUTE_PATHS } from "@app/const/routes";
import { ProjectPermissionActions, ProjectPermissionSub, useWorkspace } from "@app/context";
import { CaType, useDeleteCa, useGetCa } from "@app/hooks/api";
import { TInternalCertificateAuthority } from "@app/hooks/api/ca/types";
import { ProjectType } from "@app/hooks/api/workspace/types";
import { usePopUp } from "@app/hooks/usePopUp";
import { CaInstallCertModal } from "../CertificateAuthoritiesPage/components/CaInstallCertModal";
@@ -71,7 +70,7 @@ const Page = () => {
handlePopUpClose("deleteCa");
navigate({
to: `/projects/$projectId/${ProjectType.CertificateManager}/certificate-authorities` as const,
to: "/projects/cert-management/$projectId/certificate-authorities",
params: {
projectId
}
@@ -3,7 +3,7 @@ import { createFileRoute, linkOptions } from "@tanstack/react-router";
import { CertAuthDetailsByIDPage } from "./CertAuthDetailsByIDPage";
export const Route = createFileRoute(
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/cert-manager/_cert-manager-layout/ca/$caName"
"/_authenticate/_inject-org-details/_org-layout/projects/cert-management/$projectId/_cert-manager-layout/ca/$caName"
)({
component: CertAuthDetailsByIDPage,
beforeLoad: ({ context, params }) => {
@@ -13,7 +13,7 @@ export const Route = createFileRoute(
{
label: "Certificate Authorities",
link: linkOptions({
to: "/projects/$projectId/cert-manager/certificate-authorities",
to: "/projects/cert-management/$projectId/certificate-authorities",
params: {
projectId: params.projectId
}
@@ -30,7 +30,6 @@ import {
getCaStatusBadgeVariant
} from "@app/hooks/api/ca/constants";
import { TInternalCertificateAuthority } from "@app/hooks/api/ca/types";
import { ProjectType } from "@app/hooks/api/workspace/types";
import { UsePopUpState } from "@app/hooks/usePopUp";
type Props = {
@@ -79,7 +78,7 @@ export const CaTable = ({ handlePopUpOpen }: Props) => {
key={`ca-${ca.id}`}
onClick={() =>
navigate({
to: `/projects/$projectId/${ProjectType.CertificateManager}/ca/$caName` as const,
to: "/projects/cert-management/$projectId/ca/$caName",
params: {
projectId: currentWorkspace.id,
caName: ca.name
@@ -3,7 +3,7 @@ import { createFileRoute } from "@tanstack/react-router";
import { CertificateAuthoritiesPage } from "./CertificateAuthoritiesPage";
export const Route = createFileRoute(
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/cert-manager/_cert-manager-layout/certificate-authorities"
"/_authenticate/_inject-org-details/_org-layout/projects/cert-management/$projectId/_cert-manager-layout/certificate-authorities"
)({
component: CertificateAuthoritiesPage,
beforeLoad: ({ context }) => {
@@ -3,7 +3,7 @@ import { createFileRoute } from "@tanstack/react-router";
import { CertificatesPage } from "./CertificatesPage";
export const Route = createFileRoute(
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/cert-manager/_cert-manager-layout/certificates"
"/_authenticate/_inject-org-details/_org-layout/projects/cert-management/$projectId/_cert-manager-layout/certificates"
)({
component: CertificatesPage,
beforeLoad: ({ context }) => {
@@ -19,7 +19,6 @@ import { ROUTE_PATHS } from "@app/const/routes";
import { ProjectPermissionActions, ProjectPermissionSub, useWorkspace } from "@app/context";
import { useDeletePkiCollection, useGetPkiCollectionById } from "@app/hooks/api";
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 "../AlertingPage/components/PkiCollectionModal";
@@ -57,7 +56,7 @@ export const PkiCollectionPage = () => {
});
handlePopUpClose("deletePkiCollection");
navigate({
to: `/projects/$projectId/${ProjectType.CertificateManager}/certificates` as const,
to: "/projects/cert-management/$projectId/certificates",
params: {
projectId
}
@@ -3,7 +3,7 @@ import { createFileRoute, linkOptions } from "@tanstack/react-router";
import { PkiCollectionDetailsByIDPage } from "./PkiCollectionDetailsByIDPage";
export const Route = createFileRoute(
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/cert-manager/_cert-manager-layout/pki-collections/$collectionId"
"/_authenticate/_inject-org-details/_org-layout/projects/cert-management/$projectId/_cert-manager-layout/pki-collections/$collectionId"
)({
component: PkiCollectionDetailsByIDPage,
beforeLoad: ({ context, params }) => {
@@ -13,7 +13,7 @@ export const Route = createFileRoute(
{
label: "Certificate Collections",
link: linkOptions({
to: "/projects/$projectId/cert-manager/certificates",
to: "/projects/cert-management/$projectId/certificates",
params: {
projectId: params.projectId
}
@@ -22,7 +22,6 @@ import {
useWorkspace
} from "@app/context";
import { useDeletePkiSubscriber, useGetPkiSubscriber } from "@app/hooks/api";
import { ProjectType } from "@app/hooks/api/workspace/types";
import { usePopUp } from "@app/hooks/usePopUp";
import { PkiSubscriberModal } from "../PkiSubscribersPage/components/PkiSubscriberModal";
@@ -61,7 +60,7 @@ const Page = () => {
handlePopUpClose("deletePkiSubscriber");
navigate({
to: `/projects/$projectId/${ProjectType.CertificateManager}/subscribers` as const,
to: "/projects/cert-management/$projectId/subscribers",
params: {
projectId
}
@@ -3,7 +3,7 @@ import { createFileRoute, linkOptions } from "@tanstack/react-router";
import { PkiSubscriberDetailsByIDPage } from "./PkiSubscriberDetailsByIDPage";
export const Route = createFileRoute(
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/cert-manager/_cert-manager-layout/subscribers/$subscriberName"
"/_authenticate/_inject-org-details/_org-layout/projects/cert-management/$projectId/_cert-manager-layout/subscribers/$subscriberName"
)({
component: PkiSubscriberDetailsByIDPage,
beforeLoad: ({ context, params }) => {
@@ -13,7 +13,7 @@ export const Route = createFileRoute(
{
label: "Subscribers",
link: linkOptions({
to: "/projects/$projectId/cert-manager/subscribers",
to: "/projects/cert-management/$projectId/subscribers",
params: {
projectId: params.projectId
}
@@ -38,7 +38,6 @@ import {
PkiSubscriberStatus,
pkiSubscriberStatusToNameMap
} from "@app/hooks/api/pkiSubscriber/constants";
import { ProjectType } from "@app/hooks/api/workspace/types";
import { UsePopUpState } from "@app/hooks/usePopUp";
type Props = {
@@ -76,7 +75,7 @@ export const PkiSubscribersTable = ({ handlePopUpOpen }: Props) => {
key={`pki-subscriber-${subscriber.id}`}
onClick={() =>
navigate({
to: `/projects/$projectId/${ProjectType.CertificateManager}/subscribers/$subscriberName` as const,
to: "/projects/cert-management/$projectId/subscribers/$subscriberName",
params: {
projectId: currentWorkspace.id,
subscriberName: subscriber.name
@@ -3,7 +3,7 @@ import { createFileRoute } from "@tanstack/react-router";
import { PkiSubscribersPage } from "./PkiSubscribersPage";
export const Route = createFileRoute(
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/cert-manager/_cert-manager-layout/subscribers/"
"/_authenticate/_inject-org-details/_org-layout/projects/cert-management/$projectId/_cert-manager-layout/subscribers/"
)({
component: PkiSubscribersPage,
beforeLoad: ({ context }) => {
@@ -3,7 +3,7 @@ import { createFileRoute } from "@tanstack/react-router";
import { PkiTemplateListPage } from "./PkiTemplateListPage";
export const Route = createFileRoute(
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/cert-manager/_cert-manager-layout/certificate-templates/"
"/_authenticate/_inject-org-details/_org-layout/projects/cert-management/$projectId/_cert-manager-layout/certificate-templates/"
)({
component: PkiTemplateListPage,
beforeLoad: ({ context }) => {
@@ -2,12 +2,13 @@ import { Helmet } from "react-helmet";
import { useTranslation } from "react-i18next";
import { PageHeader, Tab, TabList, TabPanel, Tabs } from "@app/components/v2";
import { ProjectGeneralTab } from "@app/pages/project/SettingsPage/components/ProjectGeneralTab";
const tabs = [
{
name: "General",
key: "tab-project-general",
Component: () => <div className="text-white">Coming soon</div>
Component: ProjectGeneralTab
}
];
@@ -3,7 +3,7 @@ import { createFileRoute } from "@tanstack/react-router";
import { SettingsPage } from "./SettingsPage";
export const Route = createFileRoute(
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/cert-manager/_cert-manager-layout/settings"
"/_authenticate/_inject-org-details/_org-layout/projects/cert-management/$projectId/_cert-manager-layout/settings"
)({
component: SettingsPage,
beforeLoad: ({ context }) => {
+30 -2
View File
@@ -1,9 +1,37 @@
import { createFileRoute } from "@tanstack/react-router";
import { BreadcrumbTypes } from "@app/components/v2";
import { workspaceKeys } from "@app/hooks/api";
import { fetchUserProjectPermissions, roleQueryKeys } from "@app/hooks/api/roles/queries";
import { fetchWorkspaceById } from "@app/hooks/api/workspace/queries";
import { PkiManagerLayout } from "@app/layouts/PkiManagerLayout";
import { ProjectSelect } from "@app/layouts/ProjectLayout/components/ProjectSelect";
export const Route = createFileRoute(
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/cert-manager/_cert-manager-layout"
"/_authenticate/_inject-org-details/_org-layout/projects/cert-management/$projectId/_cert-manager-layout"
)({
component: PkiManagerLayout
component: PkiManagerLayout,
beforeLoad: async ({ params, context }) => {
const project = await context.queryClient.ensureQueryData({
queryKey: workspaceKeys.getWorkspaceById(params.projectId),
queryFn: () => fetchWorkspaceById(params.projectId)
});
await context.queryClient.ensureQueryData({
queryKey: roleQueryKeys.getUserProjectPermissions({
workspaceId: params.projectId
}),
queryFn: () => fetchUserProjectPermissions({ workspaceId: params.projectId })
});
return {
project,
breadcrumbs: [
{
type: BreadcrumbTypes.Component,
component: ProjectSelect
}
]
};
}
});
+1 -1
View File
@@ -3,7 +3,7 @@ import { createFileRoute } from "@tanstack/react-router";
import { KmipPage } from "./KmipPage";
export const Route = createFileRoute(
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/kms/_kms-layout/kmip"
"/_authenticate/_inject-org-details/_org-layout/projects/kms/$projectId/_kms-layout/kmip"
)({
component: KmipPage,
beforeLoad: ({ context }) => {
@@ -3,7 +3,7 @@ import { createFileRoute } from "@tanstack/react-router";
import { OverviewPage } from "./OverviewPage";
export const Route = createFileRoute(
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/kms/_kms-layout/overview"
"/_authenticate/_inject-org-details/_org-layout/projects/kms/$projectId/_kms-layout/overview"
)({
component: OverviewPage,
beforeLoad: ({ context }) => {
@@ -2,12 +2,13 @@ import { Helmet } from "react-helmet";
import { useTranslation } from "react-i18next";
import { PageHeader, Tab, TabList, TabPanel, Tabs } from "@app/components/v2";
import { ProjectGeneralTab } from "@app/pages/project/SettingsPage/components/ProjectGeneralTab";
const tabs = [
{
name: "General",
key: "tab-project-general",
Component: () => <div className="text-white">Coming soon...</div>
Component: ProjectGeneralTab
}
];
@@ -3,7 +3,7 @@ import { createFileRoute } from "@tanstack/react-router";
import { SettingsPage } from "./SettingsPage";
export const Route = createFileRoute(
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/kms/_kms-layout/settings"
"/_authenticate/_inject-org-details/_org-layout/projects/kms/$projectId/_kms-layout/settings"
)({
component: SettingsPage,
beforeLoad: ({ context }) => {
+30 -2
View File
@@ -1,9 +1,37 @@
import { createFileRoute } from "@tanstack/react-router";
import { BreadcrumbTypes } from "@app/components/v2";
import { workspaceKeys } from "@app/hooks/api";
import { fetchUserProjectPermissions, roleQueryKeys } from "@app/hooks/api/roles/queries";
import { fetchWorkspaceById } from "@app/hooks/api/workspace/queries";
import { KmsLayout } from "@app/layouts/KmsLayout";
import { ProjectSelect } from "@app/layouts/ProjectLayout/components/ProjectSelect";
export const Route = createFileRoute(
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/kms/_kms-layout"
"/_authenticate/_inject-org-details/_org-layout/projects/kms/$projectId/_kms-layout"
)({
component: KmsLayout
component: KmsLayout,
beforeLoad: async ({ params, context }) => {
const project = await context.queryClient.ensureQueryData({
queryKey: workspaceKeys.getWorkspaceById(params.projectId),
queryFn: () => fetchWorkspaceById(params.projectId)
});
await context.queryClient.ensureQueryData({
queryKey: roleQueryKeys.getUserProjectPermissions({
workspaceId: params.projectId
}),
queryFn: () => fetchUserProjectPermissions({ workspaceId: params.projectId })
});
return {
project,
breadcrumbs: [
{
type: BreadcrumbTypes.Component,
component: ProjectSelect
}
]
};
}
});
@@ -105,7 +105,7 @@ export const OrgAdminProjects = withPermission(
<TBody>
{isProjectsLoading && <TableSkeleton columns={4} innerKey="projects" />}
{!isProjectsLoading &&
projects?.map(({ name, slug, createdAt, id, defaultProduct }) => (
projects?.map(({ name, slug, createdAt, id, type }) => (
<Tr key={`project-${id}`} className="group w-full">
<Td>{name}</Td>
<Td>{slug}</Td>
@@ -126,7 +126,7 @@ export const OrgAdminProjects = withPermission(
onClick={(e) => {
e.stopPropagation();
e.preventDefault();
handleAccessProject(defaultProduct, id);
handleAccessProject(type, id);
}}
icon={<FontAwesomeIcon icon={faSignIn} />}
disabled={
@@ -21,6 +21,7 @@ import { CamundaConnectionForm } from "./CamundaConnectionForm";
import { ChecklyConnectionForm } from "./ChecklyConnectionForm";
import { CloudflareConnectionForm } from "./CloudflareConnectionForm";
import { DatabricksConnectionForm } from "./DatabricksConnectionForm";
import { DigitalOceanConnectionForm } from "./DigitalOceanConnectionForm";
import { FlyioConnectionForm } from "./FlyioConnectionForm";
import { GcpConnectionForm } from "./GcpConnectionForm";
import { GitHubConnectionForm } from "./GitHubConnectionForm";
@@ -150,6 +151,8 @@ const CreateForm = ({ app, onComplete }: CreateFormProps) => {
return <ChecklyConnectionForm onSubmit={onSubmit} />;
case AppConnection.Supabase:
return <SupabaseConnectionForm onSubmit={onSubmit} />;
case AppConnection.DigitalOcean:
return <DigitalOceanConnectionForm onSubmit={onSubmit} />;
case AppConnection.Okta:
return <OktaConnectionForm onSubmit={onSubmit} />;
default:
@@ -256,6 +259,8 @@ const UpdateForm = ({ appConnection, onComplete }: UpdateFormProps) => {
return <ChecklyConnectionForm onSubmit={onSubmit} appConnection={appConnection} />;
case AppConnection.Supabase:
return <SupabaseConnectionForm onSubmit={onSubmit} appConnection={appConnection} />;
case AppConnection.DigitalOcean:
return <DigitalOceanConnectionForm onSubmit={onSubmit} appConnection={appConnection} />;
case AppConnection.Okta:
return <OktaConnectionForm onSubmit={onSubmit} appConnection={appConnection} />;
default:
@@ -0,0 +1,136 @@
import { Controller, FormProvider, useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { z } from "zod";
import {
Button,
FormControl,
ModalClose,
SecretInput,
Select,
SelectItem
} from "@app/components/v2";
import { APP_CONNECTION_MAP, getAppConnectionMethodDetails } from "@app/helpers/appConnections";
import { AppConnection } from "@app/hooks/api/appConnections/enums";
import {
DigitalOceanConnectionMethod,
TDigitalOceanConnection
} from "@app/hooks/api/appConnections/types/digital-ocean";
import {
genericAppConnectionFieldsSchema,
GenericAppConnectionsFields
} from "./GenericAppConnectionFields";
type Props = {
appConnection?: TDigitalOceanConnection;
onSubmit: (formData: FormData) => void;
};
const rootSchema = genericAppConnectionFieldsSchema.extend({
app: z.literal(AppConnection.DigitalOcean)
});
const formSchema = z.discriminatedUnion("method", [
rootSchema.extend({
method: z.literal(DigitalOceanConnectionMethod.ApiToken),
credentials: z.object({
apiToken: z.string().trim().min(1, "API Token required")
})
})
]);
type FormData = z.infer<typeof formSchema>;
export const DigitalOceanConnectionForm = ({ appConnection, onSubmit }: Props) => {
const isUpdate = Boolean(appConnection);
const form = useForm<FormData>({
resolver: zodResolver(formSchema),
defaultValues: appConnection ?? {
app: AppConnection.DigitalOcean,
method: DigitalOceanConnectionMethod.ApiToken
}
});
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 type of token you would like to use to connect with ${
APP_CONNECTION_MAP[AppConnection.DigitalOcean].name
}. This field cannot be changed after creation.`}
errorText={error?.message}
isError={Boolean(error?.message)}
label="Token Type"
>
<Select
isDisabled={isUpdate}
value={value}
onValueChange={(val) => onChange(val)}
className="w-full border border-mineshaft-500"
position="popper"
dropdownContainerClassName="max-w-none"
>
{Object.values(DigitalOceanConnectionMethod).map((method) => {
return (
<SelectItem value={method} key={method}>
{getAppConnectionMethodDetails(method).name}{" "}
</SelectItem>
);
})}
</Select>
</FormControl>
)}
/>
<Controller
name="credentials.apiToken"
control={control}
shouldUnregister
render={({ field: { value, onChange }, fieldState: { error } }) => (
<FormControl
errorText={error?.message}
isError={Boolean(error?.message)}
label="API Key Value"
>
<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 DigitalOcean"}
</Button>
<ModalClose asChild>
<Button colorSchema="secondary" variant="plain">
Cancel
</Button>
</ModalClose>
</div>
</form>
</FormProvider>
);
};
@@ -300,10 +300,10 @@ export const LogsFilter = ({ presets, setFilter, filter }: Props) => {
onChange(e);
}}
placeholder="All projects"
options={workspacesInOrg.map(({ name, id, defaultProduct }) => ({
options={workspacesInOrg.map(({ name, id, type }) => ({
name,
id,
type: defaultProduct
type
}))}
getOptionValue={(option) => option.id}
getOptionLabel={(option) => option.name}
@@ -6,6 +6,7 @@ import { format } from "date-fns";
import { createNotification } from "@app/components/notifications";
import { IconButton, Td, Tooltip, Tr } from "@app/components/v2";
import { getProjectBaseURL } from "@app/helpers/project";
import { formatProjectRoleName } from "@app/helpers/roles";
import { useGetUserWorkspaces } from "@app/hooks/api";
import { IdentityMembership } from "@app/hooks/api/identities/types";
@@ -50,7 +51,7 @@ export const IdentityProjectRow = ({
onClick={() => {
if (isAccessible) {
navigate({
to: "/projects/$projectId/access-management",
to: `${getProjectBaseURL(project.type)}/access-management` as const,
params: {
projectId: project.id
},
@@ -38,7 +38,8 @@ export const ProjectsPage = () => {
"upgradePlan"
] as const);
const { data: serverDetails } = useFetchServerStatus();
const { data: serverDetails, isLoading } = useFetchServerStatus();
const { subscription } = useSubscription();
const isAddingProjectsAllowed = subscription?.workspaceLimit
@@ -51,7 +52,8 @@ export const ProjectsPage = () => {
<title>{t("common.head-title", { title: t("settings.members.title") })}</title>
<link rel="icon" href="/infisical.ico" />
</Helmet>
{!serverDetails?.redisConfigured && (
{!isLoading && !serverDetails?.redisConfigured && (
<div className="mb-4 flex flex-col items-start justify-start text-3xl">
<p className="mb-4 mr-4 font-semibold text-white">Announcements</p>
<div className="flex w-full items-center rounded-md border border-blue-400/70 bg-blue-900/70 p-2 text-base text-mineshaft-100">
@@ -4,6 +4,7 @@ import {
faArrowDownAZ,
faBorderAll,
faCheck,
faCheckCircle,
faFolderOpen,
faList,
faMagnifyingGlass,
@@ -16,10 +17,17 @@ import { twMerge } from "tailwind-merge";
import { createNotification } from "@app/components/notifications";
import { OrgPermissionCan } from "@app/components/permissions";
import {
Badge,
Button,
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuTrigger,
FormControl,
IconButton,
Input,
Lottie,
Modal,
ModalContent,
Pagination,
@@ -27,7 +35,7 @@ import {
Tooltip
} from "@app/components/v2";
import { OrgPermissionActions, OrgPermissionSubjects } from "@app/context";
import { getProjectHomePage } from "@app/helpers/project";
import { getProjectHomePage, getProjectLottieIcon, getProjectTitle } from "@app/helpers/project";
import {
getUserTablePreference,
PreferenceKey,
@@ -35,7 +43,7 @@ import {
} from "@app/helpers/userTablePreferences";
import { useDebounce, usePagination, usePopUp, useResetPageHelper } from "@app/hooks";
import { useRequestProjectAccess, useSearchProjects } from "@app/hooks/api";
import { Workspace } from "@app/hooks/api/workspace/types";
import { ProjectType, Workspace } from "@app/hooks/api/workspace/types";
type Props = {
onAddNewProject: () => void;
@@ -98,6 +106,7 @@ export const AllProjectView = ({
const navigate = useNavigate();
const [searchFilter, setSearchFilter] = useState("");
const [debouncedSearch] = useDebounce(searchFilter);
const [projectTypeFilter, setProjectTypeFilter] = useState<ProjectType>();
const {
setPage,
perPage,
@@ -124,7 +133,8 @@ export const AllProjectView = ({
limit,
offset,
name: debouncedSearch || undefined,
orderDirection
orderDirection,
type: projectTypeFilter
});
useResetPageHelper({
@@ -134,6 +144,9 @@ export const AllProjectView = ({
});
const requestedWorkspaceDetails = (popUp.requestAccessConfirmation.data || {}) as Workspace;
const handleToggleFilterByProjectType = (el: ProjectType) =>
setProjectTypeFilter((state) => (state === el ? undefined : el));
return (
<div>
<div className="flex w-full flex-row">
@@ -160,16 +173,62 @@ export const AllProjectView = ({
</IconButton>
</Tooltip>
</div>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<div
className={twMerge(
"ml-2 flex rounded-md border border-mineshaft-600 bg-mineshaft-800 p-1",
projectTypeFilter && "border-primary-400 text-primary-400"
)}
>
<Tooltip content="Choose visible project type" className="mb-2">
<IconButton
ariaLabel="project-types"
className={twMerge(
"min-w-[2.4rem] border-none hover:bg-mineshaft-600",
projectTypeFilter && "text-primary-400"
)}
variant="plain"
size="xs"
colorSchema="secondary"
>
<FontAwesomeIcon icon={faList} />
</IconButton>
</Tooltip>
</div>
</DropdownMenuTrigger>
<DropdownMenuContent className="thin-scrollbar overflow-y-auto" align="end">
<DropdownMenuLabel>Filter By Project Type</DropdownMenuLabel>
{Object.values(ProjectType).map((el) => (
<DropdownMenuItem
key={`filter-item-${el}`}
onClick={(e) => {
e.preventDefault();
handleToggleFilterByProjectType(el);
}}
icon={projectTypeFilter === el && <FontAwesomeIcon icon={faCheckCircle} />}
iconPos="right"
>
<div className="flex items-center gap-2">
<span>{getProjectTitle(el)}</span>
</div>
</DropdownMenuItem>
))}
</DropdownMenuContent>
</DropdownMenu>
<div className="ml-2 flex rounded-md border border-mineshaft-600 bg-mineshaft-800 p-1">
<Tooltip content="Disabled across All Project view.">
<IconButton
variant="outline_bg"
ariaLabel="grid"
size="xs"
className="min-w-[2.4rem] border-none bg-transparent hover:bg-mineshaft-600"
>
<FontAwesomeIcon icon={faBorderAll} />
</IconButton>
<div className="flex cursor-not-allowed items-center justify-center">
<IconButton
variant="outline_bg"
ariaLabel="grid"
size="xs"
isDisabled
className="pointer-events-none min-w-[2.4rem] border-none bg-transparent hover:bg-mineshaft-600"
>
<FontAwesomeIcon icon={faBorderAll} />
</IconButton>
</div>
</Tooltip>
<IconButton
variant="outline_bg"
@@ -184,7 +243,7 @@ export const AllProjectView = ({
{(isAllowed) => (
<Button
isDisabled={!isAllowed}
colorSchema="primary"
colorSchema="secondary"
leftIcon={<FontAwesomeIcon icon={faPlus} />}
onClick={() => {
if (isAddingProjectsAllowed) {
@@ -222,7 +281,7 @@ export const AllProjectView = ({
onKeyDown={(evt) => {
if (evt.key === "Enter" && workspace.isMember) {
navigate({
to: getProjectHomePage(workspace.defaultProduct),
to: getProjectHomePage(workspace.type),
params: {
projectId: workspace.id
}
@@ -232,7 +291,7 @@ export const AllProjectView = ({
onClick={() => {
if (workspace.isMember) {
navigate({
to: getProjectHomePage(workspace.defaultProduct),
to: getProjectHomePage(workspace.type),
params: {
projectId: workspace.id
}
@@ -241,38 +300,41 @@ export const AllProjectView = ({
}}
key={workspace.id}
className={twMerge(
"group flex min-w-72 grid-cols-6 items-center justify-center border-l border-r border-t border-mineshaft-600 bg-mineshaft-800 px-6 py-3 first:rounded-t-md",
"group flex min-w-72 items-center justify-center border-l border-r border-t border-mineshaft-600 bg-mineshaft-800 px-6 py-3 first:rounded-t-md",
workspace.isMember ? "cursor-pointer hover:bg-mineshaft-700" : "cursor-default"
)}
>
<div className="w-full items-center">
<div className="flex flex-grow items-center">
<div className="flex-grow truncate text-sm text-mineshaft-100">
{workspace.name}
</div>
<div className="flex items-center">
{workspace.isMember ? (
<div className="flex items-center text-center text-sm text-primary">
<FontAwesomeIcon icon={faCheck} className="mr-2" />
Joined
</div>
) : (
<div className="opacity-0 transition-all group-hover:opacity-100">
<Button
size="xs"
variant="outline_bg"
onClick={() => handlePopUpOpen("requestAccessConfirmation", workspace)}
>
Request Access
</Button>
</div>
)}
</div>
<div className="mr-3 flex min-w-0 flex-1 items-center gap-3">
<div className="rounded border border-mineshaft-500 bg-mineshaft-600 p-1 shadow-inner">
<Lottie
className="h-[1.35rem] w-[1.35rem] shrink-0"
icon={getProjectLottieIcon(workspace.type)}
/>
</div>
<div className="mt-1 max-w-lg overflow-hidden text-ellipsis whitespace-nowrap text-xs text-mineshaft-300">
{workspace.description}
<div className="-mt-0.5 flex min-w-0 flex-col">
<p className="truncate text-sm text-mineshaft-100">{workspace.name}</p>
<p className="truncate text-xs leading-4 text-mineshaft-300">
{getProjectTitle(workspace.type)}{" "}
{workspace.description ? `- ${workspace.description}` : ""}
</p>
</div>
</div>
{workspace.isMember ? (
<Badge className="flex items-center" variant="success">
<FontAwesomeIcon icon={faCheck} className="mr-1" />
<span>Joined</span>
</Badge>
) : (
<div className="opacity-0 transition-all group-hover:opacity-100">
<Button
size="xs"
variant="outline_bg"
onClick={() => handlePopUpOpen("requestAccessConfirmation", workspace)}
>
Request Access
</Button>
</div>
)}
</div>
))}
</div>
@@ -2,9 +2,9 @@ import { ReactNode, useMemo, useState } from "react";
import { faFolderOpen, faStar } from "@fortawesome/free-regular-svg-icons";
import {
faArrowDownAZ,
faArrowRight,
faArrowUpZA,
faBorderAll,
faCheckCircle,
faList,
faMagnifyingGlass,
faPlus,
@@ -13,12 +13,26 @@ import {
} from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { useNavigate } from "@tanstack/react-router";
import { twMerge } from "tailwind-merge";
import { createNotification } from "@app/components/notifications";
import { OrgPermissionCan } from "@app/components/permissions";
import { Button, IconButton, Input, Pagination, Skeleton, Tooltip } from "@app/components/v2";
import {
Button,
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuTrigger,
IconButton,
Input,
Lottie,
Pagination,
Skeleton,
Tooltip
} from "@app/components/v2";
import { OrgPermissionActions, OrgPermissionSubjects, useOrganization } from "@app/context";
import { getProjectHomePage } from "@app/helpers/project";
import { getProjectHomePage, getProjectLottieIcon, getProjectTitle } from "@app/helpers/project";
import {
getUserTablePreference,
PreferenceKey,
@@ -29,7 +43,7 @@ import { useGetUserWorkspaces } from "@app/hooks/api";
import { OrderByDirection } from "@app/hooks/api/generic/types";
import { useUpdateUserProjectFavorites } from "@app/hooks/api/users/mutation";
import { useGetUserProjectFavorites } from "@app/hooks/api/users/queries";
import { Workspace } from "@app/hooks/api/workspace/types";
import { ProjectType, Workspace } from "@app/hooks/api/workspace/types";
type Props = {
onAddNewProject: () => void;
@@ -53,6 +67,9 @@ export const MyProjectView = ({
}: Props) => {
const navigate = useNavigate();
const { currentOrg } = useOrganization();
const [projectTypeFilter, setProjectTypeFilter] = useState<Partial<Record<ProjectType, boolean>>>(
{}
);
const { data: workspaces = [], isPending: isWorkspaceLoading } = useGetUserWorkspaces();
const {
@@ -67,6 +84,7 @@ export const MyProjectView = ({
} = usePagination(ProjectOrderBy.Name, {
initPerPage: getUserTablePreference("myProjectsTable", PreferenceKey.PerPage, 24)
});
const isTableFilteredByType = Boolean(Object.values(projectTypeFilter).some((el) => el));
const handlePerPageChange = (newPerPage: number) => {
setPerPage(newPerPage);
@@ -88,13 +106,18 @@ export const MyProjectView = ({
const filteredWorkspaces = useMemo(
() =>
workspaces
.filter((ws) => ws?.name?.toLowerCase().includes(searchFilter.toLowerCase()))
.filter((ws) => {
if (isTableFilteredByType && !projectTypeFilter?.[ws.type]) {
return false;
}
return ws?.name?.toLowerCase().includes(searchFilter.toLowerCase());
})
.sort((a, b) =>
orderDirection === OrderByDirection.ASC
? a.name.toLowerCase().localeCompare(b.name.toLowerCase())
: b.name.toLowerCase().localeCompare(a.name.toLowerCase())
),
[searchFilter, orderDirection, workspaces]
[searchFilter, orderDirection, workspaces, projectTypeFilter]
);
useResetPageHelper({
@@ -117,6 +140,15 @@ export const MyProjectView = ({
};
}, [filteredWorkspaces, projectFavorites, offset, limit, page]);
const handleToggleFilterByProjectType = (type: ProjectType) => {
setProjectTypeFilter((state) => {
return {
...(state || {}),
[type]: !state?.[type]
};
});
};
const addProjectToFavorites = async (projectId: string) => {
try {
if (currentOrg?.id) {
@@ -153,61 +185,53 @@ export const MyProjectView = ({
<div
onClick={() => {
navigate({
to: getProjectHomePage(workspace.defaultProduct),
to: getProjectHomePage(workspace.type),
params: {
projectId: workspace.id
}
});
}}
key={workspace.id}
className="flex h-40 min-w-72 cursor-pointer flex-col rounded-md border border-mineshaft-600 bg-mineshaft-800 p-4"
className="cursor-pointer overflow-clip rounded border border-l-[4px] border-mineshaft-600 border-l-mineshaft-400 bg-mineshaft-800 p-4 transition-transform duration-100 hover:scale-[103%] hover:border-l-primary hover:bg-mineshaft-700"
>
<div className="flex flex-row justify-between">
<div className="mt-0 truncate text-lg text-mineshaft-100">{workspace.name}</div>
{isFavorite ? (
<FontAwesomeIcon
icon={faSolidStar}
className="text-sm text-yellow-600 hover:text-mineshaft-400"
onClick={(e) => {
e.stopPropagation();
removeProjectFromFavorites(workspace.id);
}}
<div className="flex items-center gap-4">
<div className="rounded border border-mineshaft-500 bg-mineshaft-600 p-1.5 shadow-inner">
<Lottie
className="h-[1.75rem] w-[1.75rem] shrink-0"
icon={getProjectLottieIcon(workspace.type)}
/>
) : (
<FontAwesomeIcon
icon={faStar}
className="text-sm text-mineshaft-400 hover:text-mineshaft-300"
onClick={(e) => {
e.stopPropagation();
addProjectToFavorites(workspace.id);
}}
/>
)}
</div>
<div
className="mb-2.5 mt-1 grow text-sm text-mineshaft-300"
style={{
overflow: "hidden",
display: "-webkit-box",
WebkitBoxOrient: "vertical",
WebkitLineClamp: 2
}}
>
{workspace.description}
</div>
<div className="flex w-full flex-row items-end justify-between place-self-end">
<button type="button">
<div className="group ml-auto w-max cursor-pointer rounded-full border border-mineshaft-600 bg-mineshaft-900 px-4 py-2 text-sm text-mineshaft-300 transition-all hover:border-primary-500/80 hover:bg-primary-800/20 hover:text-mineshaft-200">
Explore{" "}
</div>
<div className="min-w-0 flex-1">
<p className="truncate text-lg font-semibold text-mineshaft-100">{workspace.name}</p>
<p className="truncate text-sm leading-4 text-mineshaft-300">
{getProjectTitle(workspace.type)}
</p>
</div>
<div className="mt-0.5 self-start">
{isFavorite ? (
<FontAwesomeIcon
icon={faArrowRight}
className="pl-1.5 pr-0.5 duration-200 hover:pl-2 hover:pr-0"
icon={faSolidStar}
className="text-sm text-yellow-600 hover:text-mineshaft-400"
onClick={(e) => {
e.stopPropagation();
removeProjectFromFavorites(workspace.id);
}}
/>
</div>
</button>
) : (
<FontAwesomeIcon
icon={faStar}
className="text-sm text-mineshaft-400 hover:text-mineshaft-300"
onClick={(e) => {
e.stopPropagation();
addProjectToFavorites(workspace.id);
}}
/>
)}
</div>
</div>
<p className="mt-4 truncate text-sm text-mineshaft-400">
{workspace.description || "No description"}
</p>
</div>
);
const renderProjectListItem = (workspace: Workspace, isFavorite: boolean, index: number) => (
@@ -215,21 +239,33 @@ export const MyProjectView = ({
<div
onClick={() => {
navigate({
to: getProjectHomePage(workspace.defaultProduct),
to: getProjectHomePage(workspace.type),
params: {
projectId: workspace.id
}
});
}}
key={workspace.id}
className={`group grid h-14 min-w-72 cursor-pointer grid-cols-6 border-l border-r border-t border-mineshaft-600 bg-mineshaft-800 px-6 hover:bg-mineshaft-700 ${
className={`group flex min-w-72 cursor-pointer border-l border-r border-t border-mineshaft-600 bg-mineshaft-800 px-6 py-3 hover:bg-mineshaft-700 ${
index === 0 && "rounded-t-md"
}`}
>
<div className="flex items-center sm:col-span-3 lg:col-span-4">
<div className="truncate text-sm text-mineshaft-100">{workspace.name}</div>
<div className="flex min-w-0 flex-1 items-center gap-3">
<div className="rounded border border-mineshaft-500 bg-mineshaft-600 p-1 shadow-inner">
<Lottie
className="h-[1.35rem] w-[1.35rem] shrink-0"
icon={getProjectLottieIcon(workspace.type)}
/>
</div>
<div className="-mt-0.5 flex min-w-0 flex-col">
<p className="truncate text-sm text-mineshaft-100">{workspace.name}</p>
<p className="truncate text-xs leading-4 text-mineshaft-300">
{getProjectTitle(workspace.type)}{" "}
{workspace.description ? `- ${workspace.description}` : ""}
</p>
</div>
</div>
<div className="flex items-center justify-end sm:col-span-3 lg:col-span-2">
<div className="flex items-center justify-end">
{isFavorite ? (
<FontAwesomeIcon
icon={faSolidStar}
@@ -258,7 +294,7 @@ export const MyProjectView = ({
switch (projectsViewMode) {
case ProjectsViewMode.GRID:
projectsComponents = (
<div className="mt-4 grid w-full grid-cols-1 gap-4 lg:grid-cols-2 xl:grid-cols-3 2xl:grid-cols-4">
<div className="mt-4 grid w-full grid-cols-1 gap-5 lg:grid-cols-2 xl:grid-cols-3">
{isProjectViewLoading &&
Array.apply(0, Array(3)).map((_x, i) => (
<div
@@ -320,7 +356,18 @@ export const MyProjectView = ({
<div className="text-center font-light">No projects match search...</div>
</div>
);
} else if (filteredWorkspaces.length === 0 && isTableFilteredByType) {
projectsComponents = (
<div className="mt-4 w-full rounded-md border border-mineshaft-700 bg-mineshaft-800 px-4 py-6 text-base text-mineshaft-300">
<FontAwesomeIcon
icon={faSearch}
className="mb-4 mt-2 w-full text-center text-5xl text-mineshaft-400"
/>
<div className="text-center font-light">No projects match filters...</div>
</div>
);
}
return (
<div>
<div className="flex w-full flex-row">
@@ -351,6 +398,49 @@ export const MyProjectView = ({
</IconButton>
</Tooltip>
</div>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<div
className={twMerge(
"ml-2 flex rounded-md border border-mineshaft-600 bg-mineshaft-800 p-1",
isTableFilteredByType && "border-primary-400 text-primary-400"
)}
>
<Tooltip content="Choose visible project type" className="mb-2">
<IconButton
ariaLabel="project-types"
className={twMerge(
"min-w-[2.4rem] border-none hover:bg-mineshaft-600",
isTableFilteredByType && "text-primary-400"
)}
variant="plain"
size="xs"
colorSchema="secondary"
>
<FontAwesomeIcon icon={faList} />
</IconButton>
</Tooltip>
</div>
</DropdownMenuTrigger>
<DropdownMenuContent className="thin-scrollbar overflow-y-auto" align="end">
<DropdownMenuLabel>Filter By Project Type</DropdownMenuLabel>
{Object.values(ProjectType).map((el) => (
<DropdownMenuItem
key={`filter-item-${el}`}
onClick={(e) => {
e.preventDefault();
handleToggleFilterByProjectType(el);
}}
icon={projectTypeFilter?.[el] && <FontAwesomeIcon icon={faCheckCircle} />}
iconPos="right"
>
<div className="flex items-center gap-2">
<span>{getProjectTitle(el)}</span>
</div>
</DropdownMenuItem>
))}
</DropdownMenuContent>
</DropdownMenu>
<div className="ml-2 flex rounded-md border border-mineshaft-600 bg-mineshaft-800 p-1">
<IconButton
variant="outline_bg"
@@ -385,7 +475,7 @@ export const MyProjectView = ({
{(isAllowed) => (
<Button
isDisabled={!isAllowed}
colorSchema="primary"
colorSchema="secondary"
leftIcon={<FontAwesomeIcon icon={faPlus} />}
onClick={() => {
if (isAddingProjectsAllowed) {
@@ -417,7 +507,7 @@ export const MyProjectView = ({
onChangePerPage={handlePerPageChange}
/>
)}
{isWorkspaceEmpty && (
{isWorkspaceEmpty && !isTableFilteredByType && (
<div className="mt-4 w-full rounded-md border border-mineshaft-700 bg-mineshaft-800 px-4 py-6 text-base text-mineshaft-300">
<FontAwesomeIcon
icon={faFolderOpen}
@@ -27,7 +27,7 @@ export const ProjectListToggle = ({ value, onChange }: Props) => {
value={value}
onValueChange={onChange}
className="absolute left-0 top-0 h-full w-full cursor-pointer opacity-0"
dropdownContainerClassName="mt-10 ml-5"
position="popper"
>
<SelectItem value={ProjectListView.MyProjects}>My Projects</SelectItem>
<SelectItem value={ProjectListView.AllProjects}>All Projects</SelectItem>
@@ -1,7 +1,8 @@
import { Helmet } from "react-helmet";
import { useTranslation } from "react-i18next";
import { faCopy, faEllipsisV } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { useNavigate, useParams } from "@tanstack/react-router";
import { twMerge } from "tailwind-merge";
import { createNotification } from "@app/components/notifications";
import { OrgPermissionCan } from "@app/components/permissions";
@@ -12,8 +13,7 @@ import {
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
PageHeader,
Tooltip
PageHeader
} from "@app/components/v2";
import { ROUTE_PATHS } from "@app/const/routes";
import { OrgPermissionActions, OrgPermissionSubjects, useOrganization } from "@app/context";
@@ -22,7 +22,7 @@ import { usePopUp } from "@app/hooks/usePopUp";
import { DuplicateOrgRoleModal } from "@app/pages/organization/RoleByIDPage/components/DuplicateOrgRoleModal";
import { OrgAccessControlTabSections } from "@app/types/org";
import { RoleDetailsSection, RoleModal, RolePermissionsSection } from "./components";
import { RoleModal, RolePermissionsSection } from "./components";
export const Page = () => {
const navigate = useNavigate();
@@ -80,29 +80,64 @@ export const Page = () => {
<div className="container mx-auto flex flex-col justify-between bg-bunker-800 text-white">
{data && (
<div className="mx-auto mb-6 w-full max-w-7xl">
<PageHeader title={data.name}>
<PageHeader
title={
<div className="flex flex-col">
<div>
<span>{data.name}</span>
<p className="text-sm font-[400] normal-case leading-3 text-mineshaft-400">
{data.slug} {data.description && `- ${data.description}`}
</p>
</div>
</div>
}
>
{isCustomRole && (
<DropdownMenu>
<DropdownMenuTrigger asChild className="rounded-lg">
<div className="hover:text-primary-400 data-[state=open]:text-primary-400">
<Tooltip content="More options">
<Button variant="outline_bg">More</Button>
</Tooltip>
</div>
<DropdownMenuTrigger asChild>
<Button
colorSchema="secondary"
rightIcon={<FontAwesomeIcon icon={faEllipsisV} className="ml-2" />}
>
Options
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="start" className="p-1">
<DropdownMenuContent align="end" sideOffset={2} className="p-1">
<DropdownMenuItem
onClick={() => {
navigator.clipboard.writeText(data.id);
createNotification({
text: "Copied ID to clipboard",
type: "info"
});
}}
icon={<FontAwesomeIcon icon={faCopy} />}
>
Copy ID
</DropdownMenuItem>
<DropdownMenuItem
onClick={() => {
navigator.clipboard.writeText(data.slug);
createNotification({
text: "Copied slug to clipboard",
type: "info"
});
}}
icon={<FontAwesomeIcon icon={faCopy} />}
>
Copy Slug
</DropdownMenuItem>
<OrgPermissionCan I={OrgPermissionActions.Edit} a={OrgPermissionSubjects.Role}>
{(isAllowed) => (
<DropdownMenuItem
className={twMerge(
!isAllowed && "pointer-events-none cursor-not-allowed opacity-50"
)}
onClick={async () => {
onClick={() => {
handlePopUpOpen("role", {
roleId
});
}}
disabled={!isAllowed}
isDisabled={!isAllowed}
>
Edit Role
</DropdownMenuItem>
@@ -111,13 +146,10 @@ export const Page = () => {
<OrgPermissionCan I={OrgPermissionActions.Create} a={OrgPermissionSubjects.Role}>
{(isAllowed) => (
<DropdownMenuItem
className={twMerge(
!isAllowed && "pointer-events-none cursor-not-allowed opacity-50"
)}
onClick={() => {
handlePopUpOpen("duplicateRole");
}}
disabled={!isAllowed}
isDisabled={!isAllowed}
>
Duplicate Role
</DropdownMenuItem>
@@ -126,15 +158,10 @@ export const Page = () => {
<OrgPermissionCan I={OrgPermissionActions.Delete} a={OrgPermissionSubjects.Role}>
{(isAllowed) => (
<DropdownMenuItem
className={twMerge(
isAllowed
? "hover:!bg-red-500 hover:!text-white"
: "pointer-events-none cursor-not-allowed opacity-50"
)}
onClick={async () => {
onClick={() => {
handlePopUpOpen("deleteOrgRole");
}}
disabled={!isAllowed}
isDisabled={!isAllowed}
>
Delete Role
</DropdownMenuItem>
@@ -144,12 +171,7 @@ export const Page = () => {
</DropdownMenu>
)}
</PageHeader>
<div className="flex">
<div className="mr-4 w-96">
<RoleDetailsSection roleId={roleId} handlePopUpOpen={handlePopUpOpen} />
</div>
<RolePermissionsSection roleId={roleId} />
</div>
<RolePermissionsSection roleId={roleId} />
</div>
)}
<RoleModal popUp={popUp} handlePopUpToggle={handlePopUpToggle} />
@@ -1,95 +0,0 @@
import { faCheck, faCopy, faPencil } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { OrgPermissionCan } from "@app/components/permissions";
import { IconButton, Tooltip } from "@app/components/v2";
import { OrgPermissionActions, OrgPermissionSubjects, useOrganization } from "@app/context";
import { useTimedReset } from "@app/hooks";
import { useGetOrgRole } from "@app/hooks/api";
import { UsePopUpState } from "@app/hooks/usePopUp";
type Props = {
roleId: string;
handlePopUpOpen: (popUpName: keyof UsePopUpState<["role"]>, data?: object) => void;
};
export const RoleDetailsSection = ({ roleId, handlePopUpOpen }: Props) => {
const [copyTextId, isCopyingId, setCopyTextId] = useTimedReset<string>({
initialState: "Copy ID to clipboard"
});
const { currentOrg } = useOrganization();
const orgId = currentOrg?.id || "";
const { data } = useGetOrgRole(orgId, roleId);
const isCustomRole = !["admin", "member", "no-access"].includes(data?.slug ?? "");
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-semibold text-mineshaft-100">Org Role Details</h3>
{isCustomRole && (
<OrgPermissionCan I={OrgPermissionActions.Edit} a={OrgPermissionSubjects.Role}>
{(isAllowed) => {
return (
<Tooltip content="Edit Role">
<IconButton
isDisabled={!isAllowed}
ariaLabel="copy icon"
variant="plain"
className="group relative"
onClick={() =>
handlePopUpOpen("role", {
roleId
})
}
>
<FontAwesomeIcon icon={faPencil} />
</IconButton>
</Tooltip>
);
}}
</OrgPermissionCan>
)}
</div>
<div className="pt-4">
<div className="mb-4">
<p className="text-sm font-semibold text-mineshaft-300">Role ID</p>
<div className="group flex align-top">
<p className="text-sm text-mineshaft-300">{roleId}</p>
<div className="opacity-0 transition-opacity duration-300 group-hover:opacity-100">
<Tooltip content={copyTextId}>
<IconButton
ariaLabel="copy icon"
variant="plain"
className="group relative ml-2"
onClick={() => {
navigator.clipboard.writeText(roleId);
setCopyTextId("Copied");
}}
>
<FontAwesomeIcon icon={isCopyingId ? faCheck : faCopy} />
</IconButton>
</Tooltip>
</div>
</div>
</div>
<div className="mb-4">
<p className="text-sm font-semibold text-mineshaft-300">Name</p>
<p className="text-sm text-mineshaft-300">{data.name}</p>
</div>
<div className="mb-4">
<p className="text-sm font-semibold text-mineshaft-300">Slug</p>
<p className="text-sm text-mineshaft-300">{data.slug}</p>
</div>
<div className="mb-4">
<p className="text-sm font-semibold text-mineshaft-300">Description</p>
<p className="text-sm text-mineshaft-300">
{data.description?.length ? data.description : "-"}
</p>
</div>
</div>
</div>
) : (
<div />
);
};
@@ -76,17 +76,18 @@ export const OrgPermissionAdminConsoleRow = ({ isEditable, control, setValue }:
className="h-10 cursor-pointer transition-colors duration-100 hover:bg-mineshaft-700"
onClick={() => setIsRowExpanded.toggle()}
>
<Td>
<FontAwesomeIcon icon={isRowExpanded ? faChevronDown : faChevronRight} />
<Td className="w-4">
<FontAwesomeIcon className="w-4" icon={isRowExpanded ? faChevronDown : faChevronRight} />
</Td>
<Td>Organization Admin Console</Td>
<Td className="w-full select-none">Organization Admin Console</Td>
<Td>
<Select
value={selectedPermissionCategory}
className="w-40 bg-mineshaft-600"
dropdownContainerClassName="border border-mineshaft-600 bg-mineshaft-800"
className="h-8 w-40 bg-mineshaft-700"
dropdownContainerClassName="border text-left border-mineshaft-600 bg-mineshaft-800"
onValueChange={handlePermissionChange}
isDisabled={!isEditable}
position="popper"
>
<SelectItem value={Permission.NoAccess}>No Access</SelectItem>
<SelectItem value={Permission.Custom}>Custom</SelectItem>
@@ -95,11 +96,8 @@ export const OrgPermissionAdminConsoleRow = ({ isEditable, control, setValue }:
</Tr>
{isRowExpanded && (
<Tr>
<Td
colSpan={3}
className={`bg-bunker-600 px-0 py-0 ${isRowExpanded && "border-mineshaft-500 p-8"}`}
>
<div className="grid grid-cols-3 gap-4">
<Td colSpan={3} className="border-mineshaft-500 bg-mineshaft-900 p-8">
<div className="flex flex-grow flex-wrap justify-start gap-x-8 gap-y-4">
{PERMISSION_ACTIONS.map(({ action, label }) => {
return (
<Controller
@@ -124,17 +124,18 @@ export const OrgPermissionAppConnectionRow = ({ isEditable, control, setValue }:
className="h-10 cursor-pointer transition-colors duration-100 hover:bg-mineshaft-700"
onClick={() => setIsRowExpanded.toggle()}
>
<Td>
<FontAwesomeIcon icon={isRowExpanded ? faChevronDown : faChevronRight} />
<Td className="w-4">
<FontAwesomeIcon className="w-4" icon={isRowExpanded ? faChevronDown : faChevronRight} />
</Td>
<Td>App Connections</Td>
<Td className="w-full select-none">App Connections</Td>
<Td>
<Select
value={selectedPermissionCategory}
className="w-40 bg-mineshaft-600"
dropdownContainerClassName="border border-mineshaft-600 bg-mineshaft-800"
className="h-8 w-40 bg-mineshaft-700"
dropdownContainerClassName="border text-left border-mineshaft-600 bg-mineshaft-800"
onValueChange={handlePermissionChange}
isDisabled={!isEditable}
position="popper"
>
<SelectItem value={Permission.NoAccess}>No Access</SelectItem>
<SelectItem value={Permission.ReadOnly}>Read Only</SelectItem>
@@ -145,11 +146,8 @@ export const OrgPermissionAppConnectionRow = ({ isEditable, control, setValue }:
</Tr>
{isRowExpanded && (
<Tr>
<Td
colSpan={3}
className={`bg-bunker-600 px-0 py-0 ${isRowExpanded && "border-mineshaft-500 p-8"}`}
>
<div className="grid grid-cols-3 gap-4">
<Td colSpan={3} className="border-mineshaft-500 bg-mineshaft-900 p-8">
<div className="flex flex-grow flex-wrap justify-start gap-x-8 gap-y-4">
{PERMISSION_ACTIONS.map(({ action, label }) => {
return (
<Controller
@@ -112,17 +112,18 @@ export const OrgPermissionBillingRow = ({ isEditable, control, setValue }: Props
className="h-10 cursor-pointer transition-colors duration-100 hover:bg-mineshaft-700"
onClick={() => setIsRowExpanded.toggle()}
>
<Td>
<FontAwesomeIcon icon={isRowExpanded ? faChevronDown : faChevronRight} />
<Td className="w-4">
<FontAwesomeIcon className="w-4" icon={isRowExpanded ? faChevronDown : faChevronRight} />
</Td>
<Td>Billing</Td>
<Td className="w-full select-none">Billing</Td>
<Td>
<Select
value={selectedPermissionCategory}
className="w-40 bg-mineshaft-600"
dropdownContainerClassName="border border-mineshaft-600 bg-mineshaft-800"
className="h-8 w-40 bg-mineshaft-700"
dropdownContainerClassName="border text-left border-mineshaft-600 bg-mineshaft-800"
onValueChange={handlePermissionChange}
isDisabled={!isEditable}
position="popper"
>
<SelectItem value={Permission.NoAccess}>No Access</SelectItem>
<SelectItem value={Permission.ReadOnly}>Read Only</SelectItem>
@@ -133,11 +134,8 @@ export const OrgPermissionBillingRow = ({ isEditable, control, setValue }: Props
</Tr>
{isRowExpanded && (
<Tr>
<Td
colSpan={3}
className={`bg-bunker-600 px-0 py-0 ${isRowExpanded && "border-mineshaft-500 p-8"}`}
>
<div className="grid grid-cols-3 gap-4">
<Td colSpan={3} className="border-mineshaft-500 bg-mineshaft-900 p-8">
<div className="flex flex-grow flex-wrap justify-start gap-x-8 gap-y-4">
{PERMISSION_ACTIONS.map(({ action, label }) => {
return (
<Controller
@@ -121,17 +121,18 @@ export const OrgGatewayPermissionRow = ({ isEditable, control, setValue }: Props
className="h-10 cursor-pointer transition-colors duration-100 hover:bg-mineshaft-700"
onClick={() => setIsRowExpanded.toggle()}
>
<Td>
<FontAwesomeIcon icon={isRowExpanded ? faChevronDown : faChevronRight} />
<Td className="w-4">
<FontAwesomeIcon className="w-4" icon={isRowExpanded ? faChevronDown : faChevronRight} />
</Td>
<Td>Gateways</Td>
<Td className="w-full select-none">Gateways</Td>
<Td>
<Select
value={selectedPermissionCategory}
className="w-40 bg-mineshaft-600"
dropdownContainerClassName="border border-mineshaft-600 bg-mineshaft-800"
className="h-8 w-40 bg-mineshaft-700"
dropdownContainerClassName="border text-left border-mineshaft-600 bg-mineshaft-800"
onValueChange={handlePermissionChange}
isDisabled={!isEditable}
position="popper"
>
<SelectItem value={Permission.NoAccess}>No Access</SelectItem>
<SelectItem value={Permission.ReadOnly}>Read Only</SelectItem>
@@ -142,11 +143,8 @@ export const OrgGatewayPermissionRow = ({ isEditable, control, setValue }: Props
</Tr>
{isRowExpanded && (
<Tr>
<Td
colSpan={3}
className={`bg-bunker-600 px-0 py-0 ${isRowExpanded && "border-mineshaft-500 p-8"}`}
>
<div className="grid grid-cols-3 gap-4">
<Td colSpan={3} className="border-mineshaft-500 bg-mineshaft-900 p-8">
<div className="flex flex-grow flex-wrap justify-start gap-x-8 gap-y-4">
{PERMISSION_ACTIONS.map(({ action, label }) => {
return (
<Controller
@@ -145,17 +145,18 @@ export const OrgPermissionGroupRow = ({ isEditable, control, setValue }: Props)
className="h-10 cursor-pointer transition-colors duration-100 hover:bg-mineshaft-700"
onClick={() => setIsRowExpanded.toggle()}
>
<Td>
<FontAwesomeIcon icon={isRowExpanded ? faChevronDown : faChevronRight} />
<Td className="w-4">
<FontAwesomeIcon className="w-4" icon={isRowExpanded ? faChevronDown : faChevronRight} />
</Td>
<Td>Group Management</Td>
<Td className="w-full select-none">Group Management</Td>
<Td>
<Select
value={selectedPermissionCategory}
className="w-40 bg-mineshaft-600"
dropdownContainerClassName="border border-mineshaft-600 bg-mineshaft-800"
className="h-8 w-40 bg-mineshaft-700"
dropdownContainerClassName="border text-left border-mineshaft-600 bg-mineshaft-800"
onValueChange={handlePermissionChange}
isDisabled={!isEditable}
position="popper"
>
<SelectItem value={Permission.NoAccess}>No Access</SelectItem>
<SelectItem value={Permission.ReadOnly}>Read Only</SelectItem>
@@ -166,11 +167,8 @@ export const OrgPermissionGroupRow = ({ isEditable, control, setValue }: Props)
</Tr>
{isRowExpanded && (
<Tr>
<Td
colSpan={3}
className={`bg-bunker-600 px-0 py-0 ${isRowExpanded && "border-mineshaft-500 p-8"}`}
>
<div className="grid grid-cols-3 gap-4">
<Td colSpan={3} className="border-mineshaft-500 bg-mineshaft-900 p-8">
<div className="flex flex-grow flex-wrap justify-start gap-x-8 gap-y-4">
{PERMISSION_ACTIONS.map(({ action, label }) => {
return (
<Controller
@@ -155,17 +155,18 @@ export const OrgPermissionIdentityRow = ({ isEditable, control, setValue }: Prop
className="h-10 cursor-pointer transition-colors duration-100 hover:bg-mineshaft-700"
onClick={() => setIsRowExpanded.toggle()}
>
<Td>
<FontAwesomeIcon icon={isRowExpanded ? faChevronDown : faChevronRight} />
<Td className="w-4">
<FontAwesomeIcon className="w-4" icon={isRowExpanded ? faChevronDown : faChevronRight} />
</Td>
<Td>Machine Identity Management</Td>
<Td className="w-full select-none">Machine Identity Management</Td>
<Td>
<Select
value={selectedPermissionCategory}
className="w-40 bg-mineshaft-600"
dropdownContainerClassName="border border-mineshaft-600 bg-mineshaft-800"
className="h-8 w-40 bg-mineshaft-700"
dropdownContainerClassName="border text-left border-mineshaft-600 bg-mineshaft-800"
onValueChange={handlePermissionChange}
isDisabled={!isEditable}
position="popper"
>
<SelectItem value={Permission.NoAccess}>No Access</SelectItem>
<SelectItem value={Permission.ReadOnly}>Read Only</SelectItem>
@@ -176,11 +177,8 @@ export const OrgPermissionIdentityRow = ({ isEditable, control, setValue }: Prop
</Tr>
{isRowExpanded && (
<Tr>
<Td
colSpan={3}
className={`bg-bunker-600 px-0 py-0 ${isRowExpanded && "border-mineshaft-500 p-8"}`}
>
<div className="grid grid-cols-3 gap-4">
<Td colSpan={3} className="border-mineshaft-500 bg-mineshaft-900 p-8">
<div className="flex flex-grow flex-wrap justify-start gap-x-8 gap-y-4">
{PERMISSION_ACTIONS.map(({ action, label }) => {
return (
<Controller
@@ -73,17 +73,18 @@ export const OrgPermissionKmipRow = ({ isEditable, control, setValue }: Props) =
className="h-10 cursor-pointer transition-colors duration-100 hover:bg-mineshaft-700"
onClick={() => setIsRowExpanded.toggle()}
>
<Td>
<FontAwesomeIcon icon={isRowExpanded ? faChevronDown : faChevronRight} />
<Td className="w-4">
<FontAwesomeIcon className="w-4" icon={isRowExpanded ? faChevronDown : faChevronRight} />
</Td>
<Td>KMIP</Td>
<Td className="w-full select-none">KMIP</Td>
<Td>
<Select
value={selectedPermissionCategory}
className="w-40 bg-mineshaft-600"
dropdownContainerClassName="border border-mineshaft-600 bg-mineshaft-800"
className="h-8 w-40 bg-mineshaft-700"
dropdownContainerClassName="border text-left border-mineshaft-600 bg-mineshaft-800"
onValueChange={handlePermissionChange}
isDisabled={!isEditable}
position="popper"
>
<SelectItem value={Permission.NoAccess}>No Access</SelectItem>
<SelectItem value={Permission.Custom}>Custom</SelectItem>
@@ -92,11 +93,8 @@ export const OrgPermissionKmipRow = ({ isEditable, control, setValue }: Props) =
</Tr>
{isRowExpanded && (
<Tr>
<Td
colSpan={3}
className={`bg-bunker-600 px-0 py-0 ${isRowExpanded && "border-mineshaft-500 p-8"}`}
>
<div className="grid grid-cols-3 gap-4">
<Td colSpan={3} className="border-mineshaft-500 bg-mineshaft-900 p-8">
<div className="flex flex-grow flex-wrap justify-start gap-x-8 gap-y-4">
{PERMISSION_ACTIONS.map(({ action, label }) => {
return (
<Controller
@@ -70,17 +70,18 @@ export const OrgPermissionSecretShareRow = ({ isEditable, control, setValue }: P
className="h-10 cursor-pointer transition-colors duration-100 hover:bg-mineshaft-700"
onClick={() => setIsRowExpanded.toggle()}
>
<Td>
<FontAwesomeIcon icon={isRowExpanded ? faChevronDown : faChevronRight} />
<Td className="w-4">
<FontAwesomeIcon className="w-4" icon={isRowExpanded ? faChevronDown : faChevronRight} />
</Td>
<Td>Secret Share</Td>
<Td className="w-full select-none">Secret Share</Td>
<Td>
<Select
value={selectedPermissionCategory}
className="w-40 bg-mineshaft-600"
dropdownContainerClassName="border border-mineshaft-600 bg-mineshaft-800"
className="h-8 w-40 bg-mineshaft-700"
dropdownContainerClassName="border text-left border-mineshaft-600 bg-mineshaft-800"
onValueChange={handlePermissionChange}
isDisabled={!isEditable}
position="popper"
>
<SelectItem value={Permission.NoAccess}>No Access</SelectItem>
<SelectItem value={Permission.Custom}>Custom</SelectItem>
@@ -89,11 +90,8 @@ export const OrgPermissionSecretShareRow = ({ isEditable, control, setValue }: P
</Tr>
{isRowExpanded && (
<Tr>
<Td
colSpan={3}
className={`bg-bunker-600 px-0 py-0 ${isRowExpanded && "border-mineshaft-500 p-8"}`}
>
<div className="grid grid-cols-3 gap-4">
<Td colSpan={3} className="border-mineshaft-500 bg-mineshaft-900 p-8">
<div className="flex flex-grow flex-wrap justify-start gap-x-8 gap-y-4">
{PERMISSION_ACTIONS.map(({ action, label }) => {
return (
<Controller
@@ -70,17 +70,18 @@ export const OrgRoleWorkspaceRow = ({ isEditable, control, setValue }: Props) =>
className="h-10 cursor-pointer transition-colors duration-100 hover:bg-mineshaft-700"
onClick={() => setIsRowExpanded.toggle()}
>
<Td>
<FontAwesomeIcon icon={isRowExpanded ? faChevronDown : faChevronRight} />
<Td className="w-4">
<FontAwesomeIcon className="w-4" icon={isRowExpanded ? faChevronDown : faChevronRight} />
</Td>
<Td>Project</Td>
<Td className="w-full select-none">Project</Td>
<Td>
<Select
value={selectedPermissionCategory}
className="w-40 bg-mineshaft-600"
dropdownContainerClassName="border border-mineshaft-600 bg-mineshaft-800"
className="h-8 w-40 bg-mineshaft-700"
dropdownContainerClassName="border text-left border-mineshaft-600 bg-mineshaft-800"
onValueChange={handlePermissionChange}
isDisabled={!isEditable}
position="popper"
>
<SelectItem value={Permission.NoAccess}>No Access</SelectItem>
<SelectItem value={Permission.Custom}>Custom</SelectItem>
@@ -89,11 +90,8 @@ export const OrgRoleWorkspaceRow = ({ isEditable, control, setValue }: Props) =>
</Tr>
{isRowExpanded && (
<Tr>
<Td
colSpan={3}
className={`bg-bunker-600 px-0 py-0 ${isRowExpanded && "border-mineshaft-500 p-8"}`}
>
<div className="grid grid-cols-3 gap-4">
<Td colSpan={3} className="border-mineshaft-500 bg-mineshaft-900 p-8">
<div className="flex flex-grow flex-wrap justify-start gap-x-8 gap-y-4">
{PERMISSION_ACTIONS.map(({ action, label }) => {
return (
<Controller
@@ -158,17 +158,18 @@ export const RolePermissionRow = ({ isEditable, title, formName, control, setVal
className="h-10 cursor-pointer transition-colors duration-100 hover:bg-mineshaft-700"
onClick={() => setIsRowExpanded.toggle()}
>
<Td>
<FontAwesomeIcon icon={isRowExpanded ? faChevronDown : faChevronRight} />
<Td className="w-4">
<FontAwesomeIcon className="w-4" icon={isRowExpanded ? faChevronDown : faChevronRight} />
</Td>
<Td>{title}</Td>
<Td className="w-full select-none">{title}</Td>
<Td>
<Select
value={selectedPermissionCategory}
className="w-40 bg-mineshaft-600"
dropdownContainerClassName="border border-mineshaft-600 bg-mineshaft-800"
className="h-8 w-40 bg-mineshaft-700"
dropdownContainerClassName="border text-left border-mineshaft-600 bg-mineshaft-800"
onValueChange={handlePermissionChange}
isDisabled={!isEditable}
position="popper"
>
<SelectItem value={Permission.NoAccess}>No Access</SelectItem>
<SelectItem value={Permission.ReadOnly}>Read Only</SelectItem>
@@ -179,11 +180,8 @@ export const RolePermissionRow = ({ isEditable, title, formName, control, setVal
</Tr>
{isRowExpanded && (
<Tr>
<Td
colSpan={3}
className={`bg-bunker-600 px-0 py-0 ${isRowExpanded && "border-mineshaft-500 p-8"}`}
>
<div className="grid grid-cols-3 gap-4">
<Td colSpan={3} className="border-mineshaft-500 bg-mineshaft-900 p-8">
<div className="flex flex-grow flex-wrap justify-start gap-x-8 gap-y-4">
{getPermissionList(formName).map(({ action, label }) => {
return (
<Controller

Some files were not shown because too many files have changed in this diff Show More