mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-11 15:29:46 +00:00
Merge remote-tracking branch 'origin/main' into pki-revamp
This commit is contained in:
@@ -17,7 +17,7 @@ export const SecretSyncModalHeader = ({ destination, isConfigured }: Props) => {
|
||||
<img
|
||||
alt={`${destinationDetails.name} logo`}
|
||||
src={`/images/integrations/${destinationDetails.image}`}
|
||||
className="h-12 w-12 rounded-md bg-bunker-500 p-2"
|
||||
className="h-12 w-12 rounded-md bg-bunker-500 object-contain p-2"
|
||||
/>
|
||||
<div>
|
||||
<div className="flex items-center text-mineshaft-300">
|
||||
|
||||
+138
@@ -0,0 +1,138 @@
|
||||
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 {
|
||||
TLaravelForgeOrganization,
|
||||
TLaravelForgeServer,
|
||||
TLaravelForgeSite,
|
||||
useLaravelForgeConnectionListOrganizations,
|
||||
useLaravelForgeConnectionListServers,
|
||||
useLaravelForgeConnectionListSites
|
||||
} from "@app/hooks/api/appConnections/laravel-forge";
|
||||
import { SecretSync } from "@app/hooks/api/secretSyncs";
|
||||
|
||||
import { TSecretSyncForm } from "../schemas";
|
||||
|
||||
export const LaravelForgeSyncFields = () => {
|
||||
const { control, setValue } = useFormContext<
|
||||
TSecretSyncForm & { destination: SecretSync.LaravelForge }
|
||||
>();
|
||||
|
||||
const connectionId = useWatch({ name: "connection.id", control });
|
||||
const orgSlug = useWatch({ name: "destinationConfig.orgSlug", control });
|
||||
const serverId = useWatch({ name: "destinationConfig.serverId", control });
|
||||
|
||||
const { data: organizations, isLoading: isOrganizationsLoading } =
|
||||
useLaravelForgeConnectionListOrganizations(connectionId, {
|
||||
enabled: Boolean(connectionId)
|
||||
});
|
||||
|
||||
const { data: servers, isLoading: isServersLoading } = useLaravelForgeConnectionListServers(
|
||||
connectionId,
|
||||
orgSlug,
|
||||
{
|
||||
enabled: Boolean(connectionId && orgSlug)
|
||||
}
|
||||
);
|
||||
|
||||
const { data: sites, isLoading: isSitesLoading } = useLaravelForgeConnectionListSites(
|
||||
connectionId,
|
||||
orgSlug,
|
||||
serverId,
|
||||
{
|
||||
enabled: Boolean(connectionId && orgSlug && serverId)
|
||||
}
|
||||
);
|
||||
|
||||
const handleChangeConnection = () => {
|
||||
setValue("destinationConfig.orgSlug", "");
|
||||
setValue("destinationConfig.serverId", "");
|
||||
setValue("destinationConfig.siteId", "");
|
||||
setValue("destinationConfig.orgName", "");
|
||||
setValue("destinationConfig.serverName", "");
|
||||
setValue("destinationConfig.siteName", "");
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<SecretSyncConnectionField onChange={handleChangeConnection} />
|
||||
|
||||
<Controller
|
||||
name="destinationConfig.orgSlug"
|
||||
control={control}
|
||||
render={({ field: { value, onChange }, fieldState: { error } }) => (
|
||||
<FormControl isError={Boolean(error)} errorText={error?.message} label="Organization">
|
||||
<FilterableSelect
|
||||
menuPlacement="top"
|
||||
isLoading={isOrganizationsLoading && Boolean(connectionId)}
|
||||
isDisabled={!connectionId}
|
||||
value={organizations?.find((org) => org.slug === value) ?? null}
|
||||
onChange={(option) => {
|
||||
const selectedOrg = option as SingleValue<TLaravelForgeOrganization>;
|
||||
onChange(selectedOrg?.slug ?? "");
|
||||
setValue("destinationConfig.orgName", selectedOrg?.name ?? "");
|
||||
setValue("destinationConfig.serverId", "");
|
||||
setValue("destinationConfig.siteId", "");
|
||||
}}
|
||||
options={organizations}
|
||||
placeholder="Select an organization..."
|
||||
getOptionLabel={(option) => option.name}
|
||||
getOptionValue={(option) => option.id}
|
||||
/>
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
|
||||
<Controller
|
||||
name="destinationConfig.serverId"
|
||||
control={control}
|
||||
render={({ field: { value, onChange }, fieldState: { error } }) => (
|
||||
<FormControl isError={Boolean(error)} errorText={error?.message} label="Server">
|
||||
<FilterableSelect
|
||||
menuPlacement="top"
|
||||
isLoading={isServersLoading && Boolean(connectionId && orgSlug)}
|
||||
isDisabled={!connectionId || !orgSlug}
|
||||
value={servers?.find((server) => server.id === value) ?? null}
|
||||
onChange={(option) => {
|
||||
const selectedServer = option as SingleValue<TLaravelForgeServer>;
|
||||
onChange(selectedServer?.id ?? "");
|
||||
setValue("destinationConfig.serverName", selectedServer?.name ?? "");
|
||||
setValue("destinationConfig.siteId", "");
|
||||
}}
|
||||
options={servers}
|
||||
placeholder="Select a server..."
|
||||
getOptionLabel={(option) => option.name}
|
||||
getOptionValue={(option) => option.id}
|
||||
/>
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
|
||||
<Controller
|
||||
name="destinationConfig.siteId"
|
||||
control={control}
|
||||
render={({ field: { value, onChange }, fieldState: { error } }) => (
|
||||
<FormControl isError={Boolean(error)} errorText={error?.message} label="Site">
|
||||
<FilterableSelect
|
||||
menuPlacement="top"
|
||||
isLoading={isSitesLoading && Boolean(connectionId && orgSlug && serverId)}
|
||||
isDisabled={!connectionId || !orgSlug || !serverId}
|
||||
value={sites?.find((site) => site.id === value) ?? null}
|
||||
onChange={(option) => {
|
||||
const selectedSite = option as SingleValue<TLaravelForgeSite>;
|
||||
onChange(selectedSite?.id ?? "");
|
||||
setValue("destinationConfig.siteName", selectedSite?.name ?? "");
|
||||
}}
|
||||
options={sites}
|
||||
placeholder="Select a site..."
|
||||
getOptionLabel={(option) => option.name}
|
||||
getOptionValue={(option) => option.id}
|
||||
/>
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
+3
@@ -23,6 +23,7 @@ import { GitLabSyncFields } from "./GitLabSyncFields";
|
||||
import { HCVaultSyncFields } from "./HCVaultSyncFields";
|
||||
import { HerokuSyncFields } from "./HerokuSyncFields";
|
||||
import { HumanitecSyncFields } from "./HumanitecSyncFields";
|
||||
import { LaravelForgeSyncFields } from "./LaravelForgeSyncFields";
|
||||
import { NetlifySyncFields } from "./NetlifySyncFields";
|
||||
import { OCIVaultSyncFields } from "./OCIVaultSyncFields";
|
||||
import { RailwaySyncFields } from "./RailwaySyncFields";
|
||||
@@ -100,6 +101,8 @@ export const SecretSyncDestinationFields = () => {
|
||||
return <NetlifySyncFields />;
|
||||
case SecretSync.Bitbucket:
|
||||
return <BitbucketSyncFields />;
|
||||
case SecretSync.LaravelForge:
|
||||
return <LaravelForgeSyncFields />;
|
||||
default:
|
||||
throw new Error(`Unhandled Destination Config Field: ${destination}`);
|
||||
}
|
||||
|
||||
+1
@@ -69,6 +69,7 @@ export const SecretSyncOptionsFields = ({ hideInitialSync }: Props) => {
|
||||
case SecretSync.DigitalOceanAppPlatform:
|
||||
case SecretSync.Netlify:
|
||||
case SecretSync.Bitbucket:
|
||||
case SecretSync.LaravelForge:
|
||||
AdditionalSyncOptionsFieldsComponent = null;
|
||||
break;
|
||||
default:
|
||||
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
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 LaravelForgeSyncReviewFields = () => {
|
||||
const { watch } = useFormContext<TSecretSyncForm & { destination: SecretSync.LaravelForge }>();
|
||||
const orgName = watch("destinationConfig.orgName");
|
||||
const orgSlug = watch("destinationConfig.orgSlug");
|
||||
const serverName = watch("destinationConfig.serverName");
|
||||
const serverId = watch("destinationConfig.serverId");
|
||||
const siteName = watch("destinationConfig.siteName");
|
||||
const siteId = watch("destinationConfig.siteId");
|
||||
|
||||
return (
|
||||
<>
|
||||
<GenericFieldLabel label="Account">{orgName || orgSlug}</GenericFieldLabel>
|
||||
<GenericFieldLabel label="Server">{serverName || serverId || "None"}</GenericFieldLabel>
|
||||
<GenericFieldLabel label="Site">{siteName || siteId || "None"}</GenericFieldLabel>
|
||||
</>
|
||||
);
|
||||
};
|
||||
+4
@@ -35,6 +35,7 @@ import { GitLabSyncReviewFields } from "./GitLabSyncReviewFields";
|
||||
import { HCVaultSyncReviewFields } from "./HCVaultSyncReviewFields";
|
||||
import { HerokuSyncReviewFields } from "./HerokuSyncReviewFields";
|
||||
import { HumanitecSyncReviewFields } from "./HumanitecSyncReviewFields";
|
||||
import { LaravelForgeSyncReviewFields } from "./LaravelForgeSyncReviewFields";
|
||||
import { NetlifySyncReviewFields } from "./NetlifySyncReviewFields";
|
||||
import { OCIVaultSyncReviewFields } from "./OCIVaultSyncReviewFields";
|
||||
import { OnePassSyncReviewFields } from "./OnePassSyncReviewFields";
|
||||
@@ -168,6 +169,9 @@ export const SecretSyncReviewFields = () => {
|
||||
case SecretSync.Bitbucket:
|
||||
DestinationFieldsComponent = <BitbucketSyncReviewFields />;
|
||||
break;
|
||||
case SecretSync.LaravelForge:
|
||||
DestinationFieldsComponent = <LaravelForgeSyncReviewFields />;
|
||||
break;
|
||||
default:
|
||||
throw new Error(`Unhandled Destination Review Fields: ${destination}`);
|
||||
}
|
||||
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
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 LaravelForgeSyncDestinationSchema = BaseSecretSyncSchema().merge(
|
||||
z.object({
|
||||
destination: z.literal(SecretSync.LaravelForge),
|
||||
destinationConfig: z.object({
|
||||
orgSlug: z.string().trim().min(1, "Org Slug required"),
|
||||
orgName: z.string().trim().min(1, "Org Name required"),
|
||||
serverId: z.string().trim().min(1, "Server ID required"),
|
||||
serverName: z.string().trim().min(1, "Server Name required"),
|
||||
siteId: z.string().trim().min(1, "Site ID required"),
|
||||
siteName: z.string().trim().min(1, "Site Name required")
|
||||
})
|
||||
})
|
||||
);
|
||||
@@ -20,6 +20,7 @@ import { GitlabSyncDestinationSchema } from "./gitlab-sync-destination-schema";
|
||||
import { HCVaultSyncDestinationSchema } from "./hc-vault-sync-destination-schema";
|
||||
import { HerokuSyncDestinationSchema } from "./heroku-sync-destination-schema";
|
||||
import { HumanitecSyncDestinationSchema } from "./humanitec-sync-destination-schema";
|
||||
import { LaravelForgeSyncDestinationSchema } from "./laravel-forge-sync-destination-schema";
|
||||
import { NetlifySyncDestinationSchema } from "./netlify-sync-destination-schema";
|
||||
import { OCIVaultSyncDestinationSchema } from "./oci-vault-sync-destination-schema";
|
||||
import { RailwaySyncDestinationSchema } from "./railway-sync-destination-schema";
|
||||
@@ -61,7 +62,8 @@ const SecretSyncUnionSchema = z.discriminatedUnion("destination", [
|
||||
ChecklySyncDestinationSchema,
|
||||
DigitalOceanAppPlatformSyncDestinationSchema,
|
||||
NetlifySyncDestinationSchema,
|
||||
BitbucketSyncDestinationSchema
|
||||
BitbucketSyncDestinationSchema,
|
||||
LaravelForgeSyncDestinationSchema
|
||||
]);
|
||||
|
||||
export const SecretSyncFormSchema = SecretSyncUnionSchema;
|
||||
|
||||
@@ -17,6 +17,7 @@ import {
|
||||
} from "@app/components/v2";
|
||||
import { useProject } from "@app/context";
|
||||
import { useCreateWsTag } from "@app/hooks/api";
|
||||
import { SecretV3RawSanitized, WsTag } from "@app/hooks/api/types";
|
||||
import { slugSchema } from "@app/lib/schemas";
|
||||
|
||||
export const secretTagsColors = [
|
||||
@@ -85,6 +86,8 @@ const isValidHexColor = (hexColor: string) => {
|
||||
type Props = {
|
||||
isOpen?: boolean;
|
||||
onToggle: (isOpen: boolean) => void;
|
||||
append: (data: WsTag) => void;
|
||||
currentSecret?: SecretV3RawSanitized;
|
||||
};
|
||||
|
||||
const createTagSchema = z.object({
|
||||
@@ -100,7 +103,7 @@ type TagColor = {
|
||||
name: string;
|
||||
};
|
||||
|
||||
export const CreateTagModal = ({ isOpen, onToggle }: Props): JSX.Element => {
|
||||
export const CreateTagModal = ({ isOpen, onToggle, append, currentSecret }: Props): JSX.Element => {
|
||||
const {
|
||||
control,
|
||||
reset,
|
||||
@@ -128,11 +131,12 @@ export const CreateTagModal = ({ isOpen, onToggle }: Props): JSX.Element => {
|
||||
|
||||
const onFormSubmit = async ({ slug, color }: FormData) => {
|
||||
try {
|
||||
await createWsTag({
|
||||
const data = await createWsTag({
|
||||
projectId,
|
||||
tagColor: color,
|
||||
tagSlug: slug
|
||||
});
|
||||
append(data);
|
||||
onToggle(false);
|
||||
reset();
|
||||
createNotification({
|
||||
@@ -151,8 +155,12 @@ export const CreateTagModal = ({ isOpen, onToggle }: Props): JSX.Element => {
|
||||
return (
|
||||
<Modal isOpen={isOpen} onOpenChange={onToggle}>
|
||||
<ModalContent
|
||||
title="Create tag"
|
||||
subTitle="Specify your tag name, and the slug will be created automatically."
|
||||
title={currentSecret ? `Create tag for ${currentSecret.key}` : "Create tag"}
|
||||
subTitle={
|
||||
currentSecret
|
||||
? `Create a new tag, and it will be automatically linked to secret: ${currentSecret.key}.`
|
||||
: "Specify your tag name, and the slug will be created automatically."
|
||||
}
|
||||
>
|
||||
<form onSubmit={handleSubmit(onFormSubmit)}>
|
||||
<Controller
|
||||
@@ -253,7 +261,7 @@ export const CreateTagModal = ({ isOpen, onToggle }: Props): JSX.Element => {
|
||||
isDisabled={isSubmitting}
|
||||
isLoading={isSubmitting}
|
||||
>
|
||||
Create
|
||||
{currentSecret ? "Create and Add" : "Create"}
|
||||
</Button>
|
||||
<ModalClose asChild>
|
||||
<Button variant="plain" colorSchema="secondary">
|
||||
|
||||
@@ -17,7 +17,14 @@ const badgeVariants = cva(
|
||||
variant: {
|
||||
primary: "bg-yellow/20 text-yellow",
|
||||
danger: "bg-red/20 text-red",
|
||||
success: "bg-green/20 text-green"
|
||||
success: "bg-green/20 text-green",
|
||||
org: "bg-org-v1/20 text-org-v1 [&_svg]:text-org-v1 flex items-center opacity-100 hover:bg-org-v1/10 [&_svg]:size-3 gap-x-1 w-min whitespace-nowrap",
|
||||
namespace:
|
||||
"bg-namespace-v1/20 text-namespace-v1 [&_svg]:text-namespace-v1 flex opacity-100 hover:bg-namespace-v1/10 items-center [&_svg]:size-3.5 gap-x-1 w-min whitespace-nowrap",
|
||||
project:
|
||||
"bg-primary/10 text-primary [&_svg]:text-primary opacity-100 hover:bg-primary/10 flex items-center [&_svg]:size-3 gap-x-1 w-min whitespace-nowrap",
|
||||
instance:
|
||||
"bg-mineshaft-200/20 text-mineshaft-200 [&_svg]:text-mineshaft-200 opacity-100 hover:bg-mineshaft-200/20 flex items-center [&_svg]:size-3 gap-x-1 w-min whitespace-nowrap"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,16 +33,21 @@ export const MenuItem = <T extends ElementType = "button">({
|
||||
description,
|
||||
// wrapping in forward ref with generic component causes the loss of ts definitions on props
|
||||
inputRef,
|
||||
variant,
|
||||
...props
|
||||
}: MenuItemProps<T> & ComponentPropsWithRef<T>): JSX.Element => {
|
||||
}: MenuItemProps<T> &
|
||||
ComponentPropsWithRef<T> & { variant?: "project" | "namespace" | "org" }): JSX.Element => {
|
||||
return (
|
||||
<Item
|
||||
type="button"
|
||||
role="menuitem"
|
||||
className={twMerge(
|
||||
"group relative mt-0.5 flex w-full cursor-pointer items-center rounded-sm px-2 py-2 font-inter text-sm text-bunker-100 transition-all duration-50 hover:bg-mineshaft-700",
|
||||
"group relative mt-0.5 box-border flex w-full cursor-pointer items-center rounded-[2px] border-l-2 border-transparent px-2 py-2 font-inter text-sm text-bunker-100 transition-all duration-50 hover:bg-mineshaft-700",
|
||||
isSelected && "bg-mineshaft-600 hover:bg-mineshaft-600",
|
||||
isDisabled && "cursor-not-allowed hover:bg-transparent",
|
||||
isSelected && variant === "org" && "border-org-v1",
|
||||
isSelected && variant === "namespace" && "border-namespace-v1",
|
||||
isSelected && variant === "project" && "border-primary",
|
||||
className
|
||||
)}
|
||||
ref={inputRef}
|
||||
|
||||
@@ -1,18 +1,37 @@
|
||||
import { IconDefinition } from "@fortawesome/free-brands-svg-icons";
|
||||
import { faCube, faCubes, faGlobe, faServer } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { ReactNode } from "@tanstack/react-router";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
|
||||
import { Badge } from "@app/components/v2";
|
||||
|
||||
type Props = {
|
||||
title: ReactNode;
|
||||
description?: ReactNode;
|
||||
children?: ReactNode;
|
||||
className?: string;
|
||||
scope: "org" | "project" | "namespace" | "instance";
|
||||
};
|
||||
|
||||
export const PageHeader = ({ title, description, children, className }: Props) => (
|
||||
const SCOPE_NAME: Record<NonNullable<Props["scope"]>, { label: string; icon: IconDefinition }> = {
|
||||
org: { label: "Organization", icon: faGlobe },
|
||||
project: { label: "Project", icon: faCube },
|
||||
namespace: { label: "Namespace", icon: faCubes },
|
||||
instance: { label: "Server", icon: faServer }
|
||||
};
|
||||
|
||||
export const PageHeader = ({ title, description, children, className, scope }: Props) => (
|
||||
<div className={twMerge("mb-4 w-full", className)}>
|
||||
<div className="flex w-full justify-between">
|
||||
<div className="w-full">
|
||||
<h1 className="mr-4 text-3xl font-medium text-white capitalize">{title}</h1>
|
||||
<div className="mr-4 flex w-full items-center">
|
||||
<h1 className="text-3xl font-medium text-white capitalize">{title}</h1>
|
||||
{scope && (
|
||||
<Badge variant={scope} className="mt-1 ml-2.5">
|
||||
<FontAwesomeIcon icon={SCOPE_NAME[scope].icon} />
|
||||
{SCOPE_NAME[scope].label}
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex items-center gap-2">{children}</div>
|
||||
</div>
|
||||
|
||||
@@ -22,10 +22,18 @@ export const TabList = ({ className, children, ...props }: TabListProps) => (
|
||||
|
||||
export type TabProps = TabsPrimitive.TabsTriggerProps;
|
||||
|
||||
export const Tab = ({ className, children, ...props }: TabProps) => (
|
||||
export const Tab = ({
|
||||
className,
|
||||
children,
|
||||
variant = "project",
|
||||
...props
|
||||
}: TabProps & { variant?: "project" | "namespace" | "org" }) => (
|
||||
<TabsPrimitive.Trigger
|
||||
className={twMerge(
|
||||
"flex h-10 items-center justify-center px-3 text-sm font-medium text-mineshaft-400 transition-all select-none first:rounded-tl-md last:rounded-tr-md hover:text-mineshaft-200 data-[state=active]:border-b data-[state=active]:border-primary data-[state=active]:text-white",
|
||||
"flex h-10 items-center justify-center px-3 text-sm font-medium text-mineshaft-400 transition-all select-none first:rounded-tl-md last:rounded-tr-md hover:text-mineshaft-200 data-[state=active]:border-b data-[state=active]:text-white",
|
||||
variant === "project" && "data-[state=active]:border-primary",
|
||||
variant === "namespace" && "data-[state=active]:border-namespace-v1",
|
||||
variant === "org" && "data-[state=active]:border-org-v1",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
|
||||
@@ -48,6 +48,7 @@ import { BitbucketConnectionMethod } from "@app/hooks/api/appConnections/types/b
|
||||
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 { LaravelForgeConnectionMethod } from "@app/hooks/api/appConnections/types/laravel-forge-connection";
|
||||
import { NetlifyConnectionMethod } from "@app/hooks/api/appConnections/types/netlify-connection";
|
||||
import { OCIConnectionMethod } from "@app/hooks/api/appConnections/types/oci-connection";
|
||||
import { RailwayConnectionMethod } from "@app/hooks/api/appConnections/types/railway-connection";
|
||||
@@ -56,7 +57,13 @@ import { SupabaseConnectionMethod } from "@app/hooks/api/appConnections/types/su
|
||||
|
||||
export const APP_CONNECTION_MAP: Record<
|
||||
AppConnection,
|
||||
{ name: string; image: string; size?: number; icon?: IconDefinition; enterprise?: boolean }
|
||||
{
|
||||
name: string;
|
||||
image: string;
|
||||
size?: number;
|
||||
icon?: IconDefinition;
|
||||
enterprise?: boolean;
|
||||
}
|
||||
> = {
|
||||
[AppConnection.AWS]: { name: "AWS", image: "Amazon Web Services.png" },
|
||||
[AppConnection.GitHub]: { name: "GitHub", image: "GitHub.png" },
|
||||
@@ -115,7 +122,12 @@ export const APP_CONNECTION_MAP: Record<
|
||||
image: "Netlify.png"
|
||||
},
|
||||
[AppConnection.Okta]: { name: "Okta", image: "Okta.png" },
|
||||
[AppConnection.Redis]: { name: "Redis", image: "Redis.png" }
|
||||
[AppConnection.Redis]: { name: "Redis", image: "Redis.png" },
|
||||
[AppConnection.LaravelForge]: {
|
||||
name: "Laravel Forge",
|
||||
image: "Laravel Forge.png",
|
||||
size: 65
|
||||
}
|
||||
};
|
||||
|
||||
export const getAppConnectionMethodDetails = (method: TAppConnection["method"]) => {
|
||||
@@ -151,6 +163,7 @@ export const getAppConnectionMethodDetails = (method: TAppConnection["method"])
|
||||
case ZabbixConnectionMethod.ApiToken:
|
||||
case DigitalOceanConnectionMethod.ApiToken:
|
||||
case OktaConnectionMethod.ApiToken:
|
||||
case LaravelForgeConnectionMethod.ApiToken:
|
||||
return { name: "API Token", icon: faKey };
|
||||
case PostgresConnectionMethod.UsernameAndPassword:
|
||||
case MsSqlConnectionMethod.UsernameAndPassword:
|
||||
|
||||
@@ -113,6 +113,10 @@ export const SECRET_SYNC_MAP: Record<SecretSync, { name: string; image: string }
|
||||
[SecretSync.Bitbucket]: {
|
||||
name: "Bitbucket",
|
||||
image: "Bitbucket.png"
|
||||
},
|
||||
[SecretSync.LaravelForge]: {
|
||||
name: "Laravel Forge",
|
||||
image: "Laravel Forge.png"
|
||||
}
|
||||
};
|
||||
|
||||
@@ -146,7 +150,8 @@ export const SECRET_SYNC_CONNECTION_MAP: Record<SecretSync, AppConnection> = {
|
||||
[SecretSync.Checkly]: AppConnection.Checkly,
|
||||
[SecretSync.DigitalOceanAppPlatform]: AppConnection.DigitalOcean,
|
||||
[SecretSync.Netlify]: AppConnection.Netlify,
|
||||
[SecretSync.Bitbucket]: AppConnection.Bitbucket
|
||||
[SecretSync.Bitbucket]: AppConnection.Bitbucket,
|
||||
[SecretSync.LaravelForge]: AppConnection.LaravelForge
|
||||
};
|
||||
|
||||
export const SECRET_SYNC_INITIAL_SYNC_BEHAVIOR_MAP: Record<
|
||||
|
||||
@@ -37,5 +37,6 @@ export enum AppConnection {
|
||||
DigitalOcean = "digital-ocean",
|
||||
Netlify = "netlify",
|
||||
Okta = "okta",
|
||||
Redis = "redis"
|
||||
Redis = "redis",
|
||||
LaravelForge = "laravel-forge"
|
||||
}
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
export * from "./queries";
|
||||
export * from "./types";
|
||||
@@ -0,0 +1,104 @@
|
||||
import { useQuery, UseQueryOptions } from "@tanstack/react-query";
|
||||
|
||||
import { apiRequest } from "@app/config/request";
|
||||
import { appConnectionKeys } from "@app/hooks/api/appConnections";
|
||||
|
||||
import { TLaravelForgeOrganization, TLaravelForgeServer, TLaravelForgeSite } from "./types";
|
||||
|
||||
const laravelForgeConnectionKeys = {
|
||||
all: [...appConnectionKeys.all, "laravel-forge"] as const,
|
||||
listOrganizations: (connectionId: string) =>
|
||||
[...laravelForgeConnectionKeys.all, "organizations", connectionId] as const,
|
||||
listServers: (connectionId: string, organizationSlug: string) =>
|
||||
[...laravelForgeConnectionKeys.all, "servers", connectionId, organizationSlug] as const,
|
||||
listSites: (connectionId: string, organizationSlug: string, serverId: string) =>
|
||||
[...laravelForgeConnectionKeys.all, "sites", connectionId, organizationSlug, serverId] as const
|
||||
};
|
||||
|
||||
export const useLaravelForgeConnectionListOrganizations = (
|
||||
connectionId: string,
|
||||
options?: Omit<
|
||||
UseQueryOptions<
|
||||
TLaravelForgeOrganization[],
|
||||
unknown,
|
||||
TLaravelForgeOrganization[],
|
||||
ReturnType<typeof laravelForgeConnectionKeys.listOrganizations>
|
||||
>,
|
||||
"queryKey" | "queryFn"
|
||||
>
|
||||
) => {
|
||||
return useQuery({
|
||||
queryKey: laravelForgeConnectionKeys.listOrganizations(connectionId),
|
||||
queryFn: async () => {
|
||||
const { data } = await apiRequest.get<TLaravelForgeOrganization[]>(
|
||||
`/api/v1/app-connections/laravel-forge/${connectionId}/organizations`
|
||||
);
|
||||
|
||||
return data;
|
||||
},
|
||||
...options
|
||||
});
|
||||
};
|
||||
|
||||
export const useLaravelForgeConnectionListServers = (
|
||||
connectionId: string,
|
||||
organizationSlug: string,
|
||||
options?: Omit<
|
||||
UseQueryOptions<
|
||||
TLaravelForgeServer[],
|
||||
unknown,
|
||||
TLaravelForgeServer[],
|
||||
ReturnType<typeof laravelForgeConnectionKeys.listServers>
|
||||
>,
|
||||
"queryKey" | "queryFn"
|
||||
>
|
||||
) => {
|
||||
return useQuery({
|
||||
queryKey: laravelForgeConnectionKeys.listServers(connectionId, organizationSlug),
|
||||
queryFn: async () => {
|
||||
const params = { organizationSlug };
|
||||
const { data } = await apiRequest.get<TLaravelForgeServer[]>(
|
||||
`/api/v1/app-connections/laravel-forge/${connectionId}/servers`,
|
||||
{ params }
|
||||
);
|
||||
|
||||
return data;
|
||||
},
|
||||
enabled: Boolean(connectionId && organizationSlug),
|
||||
...options
|
||||
});
|
||||
};
|
||||
|
||||
export const useLaravelForgeConnectionListSites = (
|
||||
connectionId: string,
|
||||
organizationSlug: string,
|
||||
serverId: string,
|
||||
options?: Omit<
|
||||
UseQueryOptions<
|
||||
TLaravelForgeSite[],
|
||||
unknown,
|
||||
TLaravelForgeSite[],
|
||||
ReturnType<typeof laravelForgeConnectionKeys.listSites>
|
||||
>,
|
||||
"queryKey" | "queryFn"
|
||||
>
|
||||
) => {
|
||||
return useQuery({
|
||||
queryKey: laravelForgeConnectionKeys.listSites(connectionId, organizationSlug, serverId),
|
||||
queryFn: async () => {
|
||||
const params = {
|
||||
organizationSlug,
|
||||
serverId
|
||||
};
|
||||
|
||||
const { data } = await apiRequest.get<TLaravelForgeSite[]>(
|
||||
`/api/v1/app-connections/laravel-forge/${connectionId}/sites`,
|
||||
{ params }
|
||||
);
|
||||
|
||||
return data;
|
||||
},
|
||||
enabled: Boolean(connectionId && organizationSlug && serverId),
|
||||
...options
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,15 @@
|
||||
export type TLaravelForgeOrganization = {
|
||||
id: string;
|
||||
name: string;
|
||||
slug: string;
|
||||
};
|
||||
|
||||
export type TLaravelForgeServer = {
|
||||
id: string;
|
||||
name: string;
|
||||
};
|
||||
|
||||
export type TLaravelForgeSite = {
|
||||
id: string;
|
||||
name: string;
|
||||
};
|
||||
@@ -164,6 +164,10 @@ export type TOktaConnectionOption = TAppConnectionOptionBase & {
|
||||
app: AppConnection.Okta;
|
||||
};
|
||||
|
||||
export type TLaravelForgeConnectionOption = TAppConnectionOptionBase & {
|
||||
app: AppConnection.LaravelForge;
|
||||
};
|
||||
|
||||
export type TAzureAdCsConnectionOption = TAppConnectionOptionBase & {
|
||||
app: AppConnection.AzureADCS;
|
||||
};
|
||||
@@ -210,7 +214,8 @@ export type TAppConnectionOption =
|
||||
| TDigitalOceanConnectionOption
|
||||
| TNetlifyConnectionOption
|
||||
| TOktaConnectionOption
|
||||
| TAzureAdCsConnectionOption;
|
||||
| TAzureAdCsConnectionOption
|
||||
| TLaravelForgeConnectionOption;
|
||||
|
||||
export type TAppConnectionOptionMap = {
|
||||
[AppConnection.AWS]: TAwsConnectionOption;
|
||||
@@ -252,4 +257,5 @@ export type TAppConnectionOptionMap = {
|
||||
[AppConnection.Okta]: TOktaConnectionOption;
|
||||
[AppConnection.AzureADCS]: TAzureAdCsConnectionOption;
|
||||
[AppConnection.Redis]: TRedisConnectionOption;
|
||||
[AppConnection.LaravelForge]: TLaravelForgeConnectionOption;
|
||||
};
|
||||
|
||||
@@ -22,6 +22,7 @@ import { TGitLabConnection } from "./gitlab-connection";
|
||||
import { THCVaultConnection } from "./hc-vault-connection";
|
||||
import { THerokuConnection } from "./heroku-connection";
|
||||
import { THumanitecConnection } from "./humanitec-connection";
|
||||
import { TLaravelForgeConnection } from "./laravel-forge-connection";
|
||||
import { TLdapConnection } from "./ldap-connection";
|
||||
import { TMsSqlConnection } from "./mssql-connection";
|
||||
import { TMySqlConnection } from "./mysql-connection";
|
||||
@@ -61,6 +62,7 @@ export * from "./gitlab-connection";
|
||||
export * from "./hc-vault-connection";
|
||||
export * from "./heroku-connection";
|
||||
export * from "./humanitec-connection";
|
||||
export * from "./laravel-forge-connection";
|
||||
export * from "./ldap-connection";
|
||||
export * from "./mssql-connection";
|
||||
export * from "./mysql-connection";
|
||||
@@ -105,6 +107,7 @@ export type TAppConnection =
|
||||
| TOCIConnection
|
||||
| TOnePassConnection
|
||||
| THerokuConnection
|
||||
| TLaravelForgeConnection
|
||||
| TRenderConnection
|
||||
| TFlyioConnection
|
||||
| TGitLabConnection
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
import { AppConnection } from "@app/hooks/api/appConnections/enums";
|
||||
import { TRootAppConnection } from "@app/hooks/api/appConnections/types/root-connection";
|
||||
|
||||
export enum LaravelForgeConnectionMethod {
|
||||
ApiToken = "api-token"
|
||||
}
|
||||
|
||||
export type TLaravelForgeConnection = TRootAppConnection & { app: AppConnection.LaravelForge } & {
|
||||
method: LaravelForgeConnectionMethod.ApiToken;
|
||||
credentials: {
|
||||
apiToken: string;
|
||||
};
|
||||
};
|
||||
@@ -28,7 +28,8 @@ export enum SecretSync {
|
||||
Checkly = "checkly",
|
||||
DigitalOceanAppPlatform = "digital-ocean-app-platform",
|
||||
Netlify = "netlify",
|
||||
Bitbucket = "bitbucket"
|
||||
Bitbucket = "bitbucket",
|
||||
LaravelForge = "laravel-forge"
|
||||
}
|
||||
|
||||
export enum SecretSyncStatus {
|
||||
|
||||
@@ -21,6 +21,7 @@ import { TGitLabSync } from "./gitlab-sync";
|
||||
import { THCVaultSync } from "./hc-vault-sync";
|
||||
import { THerokuSync } from "./heroku-sync";
|
||||
import { THumanitecSync } from "./humanitec-sync";
|
||||
import { TLaravelForgeSync } from "./laravel-forge-sync";
|
||||
import { TNetlifySync } from "./netlify-sync";
|
||||
import { TOCIVaultSync } from "./oci-vault-sync";
|
||||
import { TRailwaySync } from "./railway-sync";
|
||||
@@ -69,7 +70,8 @@ export type TSecretSync =
|
||||
| TSupabaseSync
|
||||
| TDigitalOceanAppPlatformSync
|
||||
| TNetlifySync
|
||||
| TBitbucketSync;
|
||||
| TBitbucketSync
|
||||
| TLaravelForgeSync;
|
||||
|
||||
export type TListSecretSyncs = { secretSyncs: TSecretSync[] };
|
||||
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
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 TLaravelForgeSync = TRootSecretSync & {
|
||||
destination: SecretSync.LaravelForge;
|
||||
destinationConfig: {
|
||||
orgSlug: string;
|
||||
orgName: string;
|
||||
serverId: string;
|
||||
serverName: string;
|
||||
siteId: string;
|
||||
siteName: string;
|
||||
};
|
||||
connection: {
|
||||
app: AppConnection.LaravelForge;
|
||||
name: string;
|
||||
id: string;
|
||||
};
|
||||
};
|
||||
@@ -337,7 +337,8 @@ export const useMoveSecrets = ({
|
||||
destinationSecretPath,
|
||||
secretIds,
|
||||
shouldOverwrite,
|
||||
projectSlug
|
||||
projectSlug,
|
||||
projectId
|
||||
}) => {
|
||||
const { data } = await apiRequest.post<{
|
||||
isSourceUpdated: boolean;
|
||||
@@ -349,7 +350,8 @@ export const useMoveSecrets = ({
|
||||
destinationSecretPath,
|
||||
secretIds,
|
||||
shouldOverwrite,
|
||||
projectSlug
|
||||
projectSlug,
|
||||
projectId
|
||||
});
|
||||
|
||||
return data;
|
||||
|
||||
@@ -40,6 +40,8 @@
|
||||
/*legacy color schema */
|
||||
/* Fonts */
|
||||
--font-inter: "Inter", sans-serif;
|
||||
--color-org-v1: #30B3FF;
|
||||
--color-namespace-v1: #96ff59;
|
||||
|
||||
/* Primary */
|
||||
--color-primary-50: #fffff5;
|
||||
|
||||
@@ -38,7 +38,7 @@ export const KmsLayout = () => {
|
||||
}}
|
||||
>
|
||||
{({ isActive }) => (
|
||||
<MenuItem isSelected={isActive}>
|
||||
<MenuItem variant="project" isSelected={isActive}>
|
||||
<div className="mx-1 flex gap-2">
|
||||
<div className="w-6">
|
||||
<FontAwesomeIcon icon={faCube} />
|
||||
@@ -55,7 +55,7 @@ export const KmsLayout = () => {
|
||||
}}
|
||||
>
|
||||
{({ isActive }) => (
|
||||
<MenuItem isSelected={isActive}>
|
||||
<MenuItem variant="project" isSelected={isActive}>
|
||||
<div className="mx-1 flex gap-2">
|
||||
<div className="w-6">
|
||||
<FontAwesomeIcon icon={faLock} />
|
||||
@@ -74,12 +74,12 @@ export const KmsLayout = () => {
|
||||
}}
|
||||
>
|
||||
{({ isActive }) => (
|
||||
<MenuItem isSelected={isActive}>
|
||||
<MenuItem variant="project" isSelected={isActive}>
|
||||
<div className="mx-1 flex gap-2">
|
||||
<div className="w-6">
|
||||
<FontAwesomeIcon icon={faUsers} />
|
||||
</div>
|
||||
Access Management
|
||||
Project Access
|
||||
</div>
|
||||
</MenuItem>
|
||||
)}
|
||||
@@ -91,7 +91,7 @@ export const KmsLayout = () => {
|
||||
}}
|
||||
>
|
||||
{({ isActive }) => (
|
||||
<MenuItem isSelected={isActive}>
|
||||
<MenuItem variant="project" isSelected={isActive}>
|
||||
<div className="mx-1 flex gap-2">
|
||||
<div className="w-6">
|
||||
<FontAwesomeIcon icon={faBook} />
|
||||
@@ -108,12 +108,12 @@ export const KmsLayout = () => {
|
||||
}}
|
||||
>
|
||||
{({ isActive }) => (
|
||||
<MenuItem isSelected={isActive}>
|
||||
<MenuItem variant="project" isSelected={isActive}>
|
||||
<div className="mx-1 flex gap-2">
|
||||
<div className="w-6">
|
||||
<FontAwesomeIcon icon={faCog} />
|
||||
</div>
|
||||
Settings
|
||||
Project Settings
|
||||
</div>
|
||||
</MenuItem>
|
||||
)}
|
||||
@@ -125,6 +125,7 @@ export const KmsLayout = () => {
|
||||
<Menu>
|
||||
<Link to="/organization/projects">
|
||||
<MenuItem
|
||||
variant="project"
|
||||
className="relative flex items-center gap-2 overflow-hidden text-sm text-mineshaft-400 hover:text-mineshaft-300"
|
||||
leftIcon={
|
||||
<div className="w-6">
|
||||
|
||||
@@ -4,11 +4,11 @@ import { faCircleQuestion, faUserCircle } from "@fortawesome/free-regular-svg-ic
|
||||
import {
|
||||
faArrowUpRightFromSquare,
|
||||
faBook,
|
||||
faBuilding,
|
||||
faCaretDown,
|
||||
faCheck,
|
||||
faEnvelope,
|
||||
faExclamationTriangle,
|
||||
faGlobe,
|
||||
faInfo,
|
||||
faInfoCircle,
|
||||
faServer,
|
||||
@@ -20,11 +20,13 @@ import {
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { useQueryClient } from "@tanstack/react-query";
|
||||
import { Link, useLocation, useNavigate, useRouter, useRouterState } from "@tanstack/react-router";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
|
||||
import { Mfa } from "@app/components/auth/Mfa";
|
||||
import { createNotification } from "@app/components/notifications";
|
||||
import SecurityClient from "@app/components/utilities/SecurityClient";
|
||||
import {
|
||||
Badge,
|
||||
BreadcrumbContainer,
|
||||
Button,
|
||||
DropdownMenu,
|
||||
@@ -199,6 +201,8 @@ export const Navbar = () => {
|
||||
|
||||
const isServerAdminPanel = location.pathname.startsWith("/admin");
|
||||
|
||||
const isOrgScope = breadcrumbs?.length === 1; // TODO: scott/akhil is this adequate?
|
||||
|
||||
return (
|
||||
<div className="z-10 flex min-h-12 items-center border-b border-mineshaft-600 bg-mineshaft-800 px-4">
|
||||
<div>
|
||||
@@ -230,10 +234,13 @@ export const Navbar = () => {
|
||||
<DropdownMenu modal={false}>
|
||||
<Link to="/organization/projects">
|
||||
<div className="group flex cursor-pointer items-center gap-2 text-sm text-white transition-all duration-100 hover:text-primary">
|
||||
<div>
|
||||
<FontAwesomeIcon icon={faBuilding} className="text-xs text-bunker-300" />
|
||||
</div>
|
||||
<div className="whitespace-nowrap">{currentOrg?.name}</div>
|
||||
<Badge
|
||||
variant="org"
|
||||
className={twMerge("text-sm", !isOrgScope && "bg-transparent opacity-75")}
|
||||
>
|
||||
<FontAwesomeIcon icon={faGlobe} />
|
||||
{currentOrg?.name}
|
||||
</Badge>
|
||||
<div className="mr-1 rounded-sm border border-mineshaft-500 px-1 text-xs text-bunker-300 no-underline!">
|
||||
{getPlan(subscription)}
|
||||
</div>
|
||||
|
||||
@@ -52,31 +52,31 @@ export const OrgSidebar = ({ isHidden }: Props) => {
|
||||
<MenuGroup title="Overview">
|
||||
<Link to="/organization/projects">
|
||||
{({ isActive }) => (
|
||||
<MenuItem isSelected={isActive}>
|
||||
<MenuItem variant="org" isSelected={isActive}>
|
||||
<div className="mx-1 flex gap-2">
|
||||
<div className="w-6">
|
||||
<FontAwesomeIcon icon={faTable} />
|
||||
</div>
|
||||
Projects
|
||||
Overview
|
||||
</div>
|
||||
</MenuItem>
|
||||
)}
|
||||
</Link>
|
||||
<Link to="/organization/access-management">
|
||||
{({ isActive }) => (
|
||||
<MenuItem isSelected={isActive}>
|
||||
<MenuItem variant="org" isSelected={isActive}>
|
||||
<div className="mx-1 flex gap-2">
|
||||
<div className="w-6">
|
||||
<FontAwesomeIcon icon={faUsers} />
|
||||
</div>
|
||||
Access Control
|
||||
Organization Access
|
||||
</div>
|
||||
</MenuItem>
|
||||
)}
|
||||
</Link>
|
||||
<Link to="/organization/billing">
|
||||
{({ isActive }) => (
|
||||
<MenuItem isSelected={isActive}>
|
||||
<MenuItem variant="org" isSelected={isActive}>
|
||||
<div className="mx-1 flex gap-2">
|
||||
<div className="w-6">
|
||||
<FontAwesomeIcon icon={faMoneyBill} className="mr-4" />
|
||||
@@ -88,7 +88,7 @@ export const OrgSidebar = ({ isHidden }: Props) => {
|
||||
</Link>
|
||||
<Link to="/organization/audit-logs">
|
||||
{({ isActive }) => (
|
||||
<MenuItem isSelected={isActive}>
|
||||
<MenuItem variant="org" isSelected={isActive}>
|
||||
<div className="mx-1 flex gap-2">
|
||||
<div className="w-6">
|
||||
<FontAwesomeIcon icon={faBook} className="mr-4" />
|
||||
@@ -100,7 +100,7 @@ export const OrgSidebar = ({ isHidden }: Props) => {
|
||||
</Link>
|
||||
<Link to="/organization/settings">
|
||||
{({ isActive }) => (
|
||||
<MenuItem isSelected={isActive}>
|
||||
<MenuItem variant="org" isSelected={isActive}>
|
||||
<div className="mx-1 flex gap-2">
|
||||
<div className="w-6">
|
||||
<FontAwesomeIcon icon={faCog} className="mr-4" />
|
||||
@@ -114,7 +114,7 @@ export const OrgSidebar = ({ isHidden }: Props) => {
|
||||
<MenuGroup title="Resources">
|
||||
<Link to="/organization/app-connections">
|
||||
{({ isActive }) => (
|
||||
<MenuItem isSelected={isActive}>
|
||||
<MenuItem variant="org" isSelected={isActive}>
|
||||
<div className="mx-1 flex gap-2">
|
||||
<div className="w-6">
|
||||
<FontAwesomeIcon icon={faPlug} className="mr-4" />
|
||||
@@ -126,7 +126,7 @@ export const OrgSidebar = ({ isHidden }: Props) => {
|
||||
</Link>
|
||||
<Link to="/organization/networking">
|
||||
{({ isActive }) => (
|
||||
<MenuItem isSelected={isActive}>
|
||||
<MenuItem variant="org" isSelected={isActive}>
|
||||
<div className="mx-1 flex gap-2">
|
||||
<div className="w-6">
|
||||
<FontAwesomeIcon icon={faNetworkWired} className="mr-4" />
|
||||
@@ -145,6 +145,7 @@ export const OrgSidebar = ({ isHidden }: Props) => {
|
||||
!subscription.has_used_trial && (
|
||||
<Tooltip content="Start Free Pro Trial">
|
||||
<MenuItem
|
||||
variant="org"
|
||||
className="relative flex items-center gap-2 overflow-hidden text-sm text-mineshaft-400 hover:text-mineshaft-300"
|
||||
leftIcon={
|
||||
<FontAwesomeIcon
|
||||
@@ -170,6 +171,7 @@ export const OrgSidebar = ({ isHidden }: Props) => {
|
||||
)}
|
||||
<Link to="/organization/secret-sharing">
|
||||
<MenuItem
|
||||
variant="org"
|
||||
className="relative flex items-center gap-2 overflow-hidden text-sm text-mineshaft-400 hover:text-mineshaft-300"
|
||||
leftIcon={
|
||||
<div className="w-6">
|
||||
@@ -183,6 +185,7 @@ export const OrgSidebar = ({ isHidden }: Props) => {
|
||||
{user.superAdmin && (
|
||||
<Link to="/admin">
|
||||
<MenuItem
|
||||
variant="org"
|
||||
className="relative flex items-center gap-2 overflow-hidden text-sm text-mineshaft-400 hover:text-mineshaft-300"
|
||||
leftIcon={
|
||||
<div className="w-6">
|
||||
|
||||
@@ -59,7 +59,7 @@ export const PamLayout = () => {
|
||||
}}
|
||||
>
|
||||
{({ isActive }) => (
|
||||
<MenuItem isSelected={isActive}>
|
||||
<MenuItem variant="project" isSelected={isActive}>
|
||||
<div className="mx-1 flex gap-2">
|
||||
<div className="w-6">
|
||||
<FontAwesomeIcon icon={faUser} />
|
||||
@@ -76,7 +76,7 @@ export const PamLayout = () => {
|
||||
}}
|
||||
>
|
||||
{({ isActive }) => (
|
||||
<MenuItem isSelected={isActive}>
|
||||
<MenuItem variant="project" isSelected={isActive}>
|
||||
<div className="mx-1 flex gap-2">
|
||||
<div className="w-6">
|
||||
<FontAwesomeIcon icon={faBoxOpen} />
|
||||
@@ -93,7 +93,7 @@ export const PamLayout = () => {
|
||||
}}
|
||||
>
|
||||
{({ isActive }) => (
|
||||
<MenuItem isSelected={isActive}>
|
||||
<MenuItem variant="project" isSelected={isActive}>
|
||||
<div className="mx-1 flex gap-2">
|
||||
<div className="w-6">
|
||||
<FontAwesomeIcon icon={faDisplay} />
|
||||
@@ -112,12 +112,12 @@ export const PamLayout = () => {
|
||||
}}
|
||||
>
|
||||
{({ isActive }) => (
|
||||
<MenuItem isSelected={isActive}>
|
||||
<MenuItem variant="project" isSelected={isActive}>
|
||||
<div className="mx-1 flex gap-2">
|
||||
<div className="w-6">
|
||||
<FontAwesomeIcon icon={faUsers} />
|
||||
</div>
|
||||
Access Management
|
||||
Project Access
|
||||
</div>
|
||||
</MenuItem>
|
||||
)}
|
||||
@@ -129,7 +129,7 @@ export const PamLayout = () => {
|
||||
}}
|
||||
>
|
||||
{({ isActive }) => (
|
||||
<MenuItem isSelected={isActive}>
|
||||
<MenuItem variant="project" isSelected={isActive}>
|
||||
<div className="mx-1 flex gap-2">
|
||||
<div className="w-6">
|
||||
<FontAwesomeIcon icon={faBook} />
|
||||
@@ -146,12 +146,12 @@ export const PamLayout = () => {
|
||||
}}
|
||||
>
|
||||
{({ isActive }) => (
|
||||
<MenuItem isSelected={isActive}>
|
||||
<MenuItem variant="project" isSelected={isActive}>
|
||||
<div className="mx-1 flex gap-2">
|
||||
<div className="w-6">
|
||||
<FontAwesomeIcon icon={faCog} />
|
||||
</div>
|
||||
Settings
|
||||
Project Settings
|
||||
</div>
|
||||
</MenuItem>
|
||||
)}
|
||||
|
||||
@@ -70,7 +70,7 @@ export const PkiManagerLayout = () => {
|
||||
}}
|
||||
>
|
||||
{({ isActive }) => (
|
||||
<MenuItem isSelected={isActive}>
|
||||
<MenuItem variant="project" isSelected={isActive}>
|
||||
<div className="mx-1 flex gap-2">
|
||||
<div className="w-6">
|
||||
<FontAwesomeIcon icon={faFileLines} />
|
||||
@@ -87,7 +87,7 @@ export const PkiManagerLayout = () => {
|
||||
}}
|
||||
>
|
||||
{({ isActive }) => (
|
||||
<MenuItem isSelected={isActive}>
|
||||
<MenuItem variant="project" isSelected={isActive}>
|
||||
<div className="mx-1 flex gap-2">
|
||||
<div className="w-6">
|
||||
<FontAwesomeIcon icon={faCertificate} />
|
||||
@@ -104,7 +104,7 @@ export const PkiManagerLayout = () => {
|
||||
}}
|
||||
>
|
||||
{({ isActive }) => (
|
||||
<MenuItem isSelected={isActive}>
|
||||
<MenuItem variant="project" isSelected={isActive}>
|
||||
<div className="mx-1 flex gap-2">
|
||||
<div className="w-6">
|
||||
<FontAwesomeIcon icon={faStamp} />
|
||||
@@ -121,7 +121,7 @@ export const PkiManagerLayout = () => {
|
||||
}}
|
||||
>
|
||||
{({ isActive }) => (
|
||||
<MenuItem isSelected={isActive}>
|
||||
<MenuItem variant="project" isSelected={isActive}>
|
||||
<div className="mx-1 flex gap-2">
|
||||
<div className="w-6">
|
||||
<FontAwesomeIcon icon={faBell} />
|
||||
@@ -138,7 +138,7 @@ export const PkiManagerLayout = () => {
|
||||
}}
|
||||
>
|
||||
{({ isActive }) => (
|
||||
<MenuItem isSelected={isActive}>
|
||||
<MenuItem variant="project" isSelected={isActive}>
|
||||
<div className="mx-1 flex gap-2">
|
||||
<div className="w-6">
|
||||
<FontAwesomeIcon icon={faPuzzlePiece} />
|
||||
@@ -155,7 +155,7 @@ export const PkiManagerLayout = () => {
|
||||
}}
|
||||
>
|
||||
{({ isActive }) => (
|
||||
<MenuItem isSelected={isActive}>
|
||||
<MenuItem variant="project" isSelected={isActive}>
|
||||
<div className="mx-1 flex gap-2">
|
||||
<div className="w-6">
|
||||
<FontAwesomeIcon icon={faPlug} />
|
||||
@@ -216,12 +216,12 @@ export const PkiManagerLayout = () => {
|
||||
}}
|
||||
>
|
||||
{({ isActive }) => (
|
||||
<MenuItem isSelected={isActive}>
|
||||
<MenuItem variant="project" isSelected={isActive}>
|
||||
<div className="mx-1 flex gap-2">
|
||||
<div className="w-6">
|
||||
<FontAwesomeIcon icon={faUsers} />
|
||||
</div>
|
||||
Access Management
|
||||
Project Access
|
||||
</div>
|
||||
</MenuItem>
|
||||
)}
|
||||
@@ -233,7 +233,7 @@ export const PkiManagerLayout = () => {
|
||||
}}
|
||||
>
|
||||
{({ isActive }) => (
|
||||
<MenuItem isSelected={isActive}>
|
||||
<MenuItem variant="project" isSelected={isActive}>
|
||||
<div className="mx-1 flex gap-2">
|
||||
<div className="w-6">
|
||||
<FontAwesomeIcon icon={faBook} />
|
||||
@@ -250,12 +250,12 @@ export const PkiManagerLayout = () => {
|
||||
}}
|
||||
>
|
||||
{({ isActive }) => (
|
||||
<MenuItem isSelected={isActive}>
|
||||
<MenuItem variant="project" isSelected={isActive}>
|
||||
<div className="mx-1 flex gap-2">
|
||||
<div className="w-6">
|
||||
<FontAwesomeIcon icon={faCog} />
|
||||
</div>
|
||||
Settings
|
||||
Project Settings
|
||||
</div>
|
||||
</MenuItem>
|
||||
)}
|
||||
|
||||
@@ -3,10 +3,10 @@ import { faStar } from "@fortawesome/free-regular-svg-icons";
|
||||
import {
|
||||
faCaretDown,
|
||||
faCheck,
|
||||
faCube,
|
||||
faMagnifyingGlass,
|
||||
faPlus,
|
||||
faStar as faSolidStar,
|
||||
faTable
|
||||
faStar as faSolidStar
|
||||
} from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { Link, linkOptions } from "@tanstack/react-router";
|
||||
@@ -16,6 +16,7 @@ import { createNotification } from "@app/components/notifications";
|
||||
import { OrgPermissionCan } from "@app/components/permissions";
|
||||
import { NewProjectModal } from "@app/components/projects";
|
||||
import {
|
||||
Badge,
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
@@ -106,14 +107,16 @@ export const ProjectSelect = () => {
|
||||
projectId: currentWorkspace.id
|
||||
}}
|
||||
>
|
||||
<div className="flex cursor-pointer items-center gap-2 text-sm text-white duration-100 hover:text-primary">
|
||||
<div>
|
||||
<FontAwesomeIcon icon={faTable} className="text-xs text-bunker-300" />
|
||||
</div>
|
||||
<div className="relative flex cursor-pointer items-center gap-2 text-sm text-white duration-100 hover:text-primary">
|
||||
<Tooltip content={currentWorkspace.name} className="max-w-96 break-words">
|
||||
<div className="max-w-44 overflow-hidden text-ellipsis whitespace-nowrap">
|
||||
<Badge
|
||||
variant="project"
|
||||
className="max-w-44 overflow-hidden text-sm text-ellipsis whitespace-nowrap"
|
||||
>
|
||||
<FontAwesomeIcon icon={faCube} />
|
||||
|
||||
{currentWorkspace?.name}
|
||||
</div>
|
||||
</Badge>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</Link>
|
||||
|
||||
@@ -82,6 +82,7 @@ export const SecretManagerLayout = () => {
|
||||
>
|
||||
{({ isActive }) => (
|
||||
<MenuItem
|
||||
variant="project"
|
||||
isSelected={
|
||||
isActive ||
|
||||
location.pathname.startsWith(
|
||||
@@ -105,7 +106,7 @@ export const SecretManagerLayout = () => {
|
||||
}}
|
||||
>
|
||||
{({ isActive }) => (
|
||||
<MenuItem isSelected={isActive}>
|
||||
<MenuItem variant="project" isSelected={isActive}>
|
||||
<div className="mx-1 flex gap-2">
|
||||
<div className="w-6">
|
||||
<FontAwesomeIcon icon={faPuzzlePiece} />
|
||||
@@ -123,7 +124,7 @@ export const SecretManagerLayout = () => {
|
||||
}}
|
||||
>
|
||||
{({ isActive }) => (
|
||||
<MenuItem isSelected={isActive}>
|
||||
<MenuItem variant="project" isSelected={isActive}>
|
||||
<div className="mx-1 flex gap-2">
|
||||
<div className="w-6">
|
||||
<FontAwesomeIcon icon={faArrowsSpin} />
|
||||
@@ -141,7 +142,7 @@ export const SecretManagerLayout = () => {
|
||||
}}
|
||||
>
|
||||
{({ isActive }) => (
|
||||
<MenuItem isSelected={isActive}>
|
||||
<MenuItem variant="project" isSelected={isActive}>
|
||||
<div className="mx-1 flex gap-2">
|
||||
<div className="w-6">
|
||||
<FontAwesomeIcon icon={faCheckToSlot} />
|
||||
@@ -166,7 +167,7 @@ export const SecretManagerLayout = () => {
|
||||
}}
|
||||
>
|
||||
{({ isActive }) => (
|
||||
<MenuItem isSelected={isActive}>
|
||||
<MenuItem variant="project" isSelected={isActive}>
|
||||
<div className="mx-1 flex gap-2">
|
||||
<div className="w-6">
|
||||
<FontAwesomeIcon icon={faPlug} />
|
||||
@@ -185,12 +186,12 @@ export const SecretManagerLayout = () => {
|
||||
}}
|
||||
>
|
||||
{({ isActive }) => (
|
||||
<MenuItem isSelected={isActive}>
|
||||
<MenuItem variant="project" isSelected={isActive}>
|
||||
<div className="mx-1 flex gap-2">
|
||||
<div className="w-6">
|
||||
<FontAwesomeIcon icon={faUsers} />
|
||||
</div>
|
||||
Access Management
|
||||
Project Access
|
||||
</div>
|
||||
</MenuItem>
|
||||
)}
|
||||
@@ -202,7 +203,7 @@ export const SecretManagerLayout = () => {
|
||||
}}
|
||||
>
|
||||
{({ isActive }) => (
|
||||
<MenuItem isSelected={isActive}>
|
||||
<MenuItem variant="project" isSelected={isActive}>
|
||||
<div className="mx-1 flex gap-2">
|
||||
<div className="w-6">
|
||||
<FontAwesomeIcon icon={faBook} />
|
||||
@@ -219,12 +220,12 @@ export const SecretManagerLayout = () => {
|
||||
}}
|
||||
>
|
||||
{({ isActive }) => (
|
||||
<MenuItem isSelected={isActive}>
|
||||
<MenuItem variant="project" isSelected={isActive}>
|
||||
<div className="mx-1 flex gap-2">
|
||||
<div className="w-6">
|
||||
<FontAwesomeIcon icon={faCog} />
|
||||
</div>
|
||||
Settings
|
||||
Project Settings
|
||||
</div>
|
||||
</MenuItem>
|
||||
)}
|
||||
@@ -236,6 +237,7 @@ export const SecretManagerLayout = () => {
|
||||
<Menu>
|
||||
<Link to="/organization/projects">
|
||||
<MenuItem
|
||||
variant="project"
|
||||
className="relative flex items-center gap-2 overflow-hidden text-sm text-mineshaft-400 hover:text-mineshaft-300"
|
||||
leftIcon={
|
||||
<div className="w-6">
|
||||
|
||||
@@ -69,7 +69,7 @@ export const SecretScanningLayout = () => {
|
||||
}}
|
||||
>
|
||||
{({ isActive }) => (
|
||||
<MenuItem isSelected={isActive}>
|
||||
<MenuItem variant="project" isSelected={isActive}>
|
||||
<div className="mx-1 flex gap-2">
|
||||
<div className="w-6">
|
||||
<FontAwesomeIcon icon={faDatabase} />
|
||||
@@ -86,7 +86,7 @@ export const SecretScanningLayout = () => {
|
||||
}}
|
||||
>
|
||||
{({ isActive }) => (
|
||||
<MenuItem isSelected={isActive}>
|
||||
<MenuItem variant="project" isSelected={isActive}>
|
||||
<div className="mx-1 flex w-full gap-2">
|
||||
<div className="w-6">
|
||||
<FontAwesomeIcon icon={faMagnifyingGlass} />
|
||||
@@ -108,7 +108,7 @@ export const SecretScanningLayout = () => {
|
||||
}}
|
||||
>
|
||||
{({ isActive }) => (
|
||||
<MenuItem isSelected={isActive}>
|
||||
<MenuItem variant="project" isSelected={isActive}>
|
||||
<div className="mx-1 flex gap-2">
|
||||
<div className="w-6">
|
||||
<FontAwesomeIcon icon={faPlug} />
|
||||
@@ -127,12 +127,12 @@ export const SecretScanningLayout = () => {
|
||||
}}
|
||||
>
|
||||
{({ isActive }) => (
|
||||
<MenuItem isSelected={isActive}>
|
||||
<MenuItem variant="project" isSelected={isActive}>
|
||||
<div className="mx-1 flex gap-2">
|
||||
<div className="w-6">
|
||||
<FontAwesomeIcon icon={faUsers} />
|
||||
</div>
|
||||
Access Management
|
||||
Project Access
|
||||
</div>
|
||||
</MenuItem>
|
||||
)}
|
||||
@@ -144,7 +144,7 @@ export const SecretScanningLayout = () => {
|
||||
}}
|
||||
>
|
||||
{({ isActive }) => (
|
||||
<MenuItem isSelected={isActive}>
|
||||
<MenuItem variant="project" isSelected={isActive}>
|
||||
<div className="mx-1 flex gap-2">
|
||||
<div className="w-6">
|
||||
<FontAwesomeIcon icon={faBook} />
|
||||
@@ -166,7 +166,7 @@ export const SecretScanningLayout = () => {
|
||||
<div className="w-6">
|
||||
<FontAwesomeIcon icon={faCog} />
|
||||
</div>
|
||||
Settings
|
||||
Project Settings
|
||||
</div>
|
||||
</MenuItem>
|
||||
)}
|
||||
@@ -178,6 +178,7 @@ export const SecretScanningLayout = () => {
|
||||
<Menu>
|
||||
<Link to="/organization/projects">
|
||||
<MenuItem
|
||||
variant="project"
|
||||
className="relative flex items-center gap-2 overflow-hidden text-sm text-mineshaft-400 hover:text-mineshaft-300"
|
||||
leftIcon={
|
||||
<div className="w-6">
|
||||
|
||||
@@ -51,7 +51,7 @@ export const SshLayout = () => {
|
||||
}}
|
||||
>
|
||||
{({ isActive }) => (
|
||||
<MenuItem isSelected={isActive}>
|
||||
<MenuItem variant="project" isSelected={isActive}>
|
||||
<div className="mx-1 flex gap-2">
|
||||
<div className="w-6">
|
||||
<FontAwesomeIcon icon={faServer} />
|
||||
@@ -74,7 +74,7 @@ export const SshLayout = () => {
|
||||
}}
|
||||
>
|
||||
{({ isActive }) => (
|
||||
<MenuItem isSelected={isActive}>
|
||||
<MenuItem variant="project" isSelected={isActive}>
|
||||
<div className="mx-1 flex gap-2">
|
||||
<div className="w-6">
|
||||
<FontAwesomeIcon icon={faStamp} />
|
||||
@@ -97,12 +97,12 @@ export const SshLayout = () => {
|
||||
}}
|
||||
>
|
||||
{({ isActive }) => (
|
||||
<MenuItem isSelected={isActive}>
|
||||
<MenuItem variant="project" isSelected={isActive}>
|
||||
<div className="mx-1 flex gap-2">
|
||||
<div className="w-6">
|
||||
<FontAwesomeIcon icon={faUsers} />
|
||||
</div>
|
||||
Access Management
|
||||
Project Access
|
||||
</div>
|
||||
</MenuItem>
|
||||
)}
|
||||
@@ -114,7 +114,7 @@ export const SshLayout = () => {
|
||||
}}
|
||||
>
|
||||
{({ isActive }) => (
|
||||
<MenuItem isSelected={isActive}>
|
||||
<MenuItem variant="project" isSelected={isActive}>
|
||||
<div className="mx-1 flex gap-2">
|
||||
<div className="w-6">
|
||||
<FontAwesomeIcon icon={faBook} />
|
||||
@@ -131,12 +131,12 @@ export const SshLayout = () => {
|
||||
}}
|
||||
>
|
||||
{({ isActive }) => (
|
||||
<MenuItem isSelected={isActive}>
|
||||
<MenuItem variant="project" isSelected={isActive}>
|
||||
<div className="mx-1 flex gap-2">
|
||||
<div className="w-6">
|
||||
<FontAwesomeIcon icon={faCog} />
|
||||
</div>
|
||||
Settings
|
||||
Project Settings
|
||||
</div>
|
||||
</MenuItem>
|
||||
)}
|
||||
@@ -148,6 +148,7 @@ export const SshLayout = () => {
|
||||
<Menu>
|
||||
<Link to="/organization/projects">
|
||||
<MenuItem
|
||||
variant="project"
|
||||
className="relative flex items-center gap-2 overflow-hidden text-sm text-mineshaft-400 hover:text-mineshaft-300"
|
||||
leftIcon={
|
||||
<div className="w-6">
|
||||
|
||||
@@ -16,6 +16,7 @@ export const AccessManagementPage = () => {
|
||||
<div className="container mx-auto flex flex-col justify-between bg-bunker-800 text-white">
|
||||
<div className="mx-auto mb-6 w-full max-w-7xl">
|
||||
<PageHeader
|
||||
scope="instance"
|
||||
title="Access Control"
|
||||
description="Manage server admins within your Infisical instance."
|
||||
/>
|
||||
|
||||
@@ -16,6 +16,7 @@ export const AuthenticationPage = () => {
|
||||
<div className="container mx-auto flex flex-col justify-between bg-bunker-800 text-white">
|
||||
<div className="mx-auto mb-6 w-full max-w-7xl">
|
||||
<PageHeader
|
||||
scope="instance"
|
||||
title="Authentication"
|
||||
description="Manage authentication settings for your Infisical instance."
|
||||
/>
|
||||
|
||||
@@ -15,7 +15,11 @@ export const CachingPage = () => {
|
||||
</Helmet>
|
||||
<div className="container mx-auto flex flex-col justify-between bg-bunker-800 text-white">
|
||||
<div className="mx-auto mb-6 w-full max-w-7xl">
|
||||
<PageHeader title="Caching" description="Manage caching for your Infisical instance." />
|
||||
<PageHeader
|
||||
scope="instance"
|
||||
title="Caching"
|
||||
description="Manage caching for your Infisical instance."
|
||||
/>
|
||||
<CachingPageForm />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -16,6 +16,7 @@ export const EncryptionPage = () => {
|
||||
<div className="container mx-auto flex flex-col justify-between bg-bunker-800 text-white">
|
||||
<div className="mx-auto mb-6 w-full max-w-7xl">
|
||||
<PageHeader
|
||||
scope="instance"
|
||||
title="Encryption"
|
||||
description="Manage encryption settings for your Infisical instance."
|
||||
/>
|
||||
|
||||
@@ -16,6 +16,7 @@ export const EnvironmentPage = () => {
|
||||
<div className="container mx-auto flex flex-col justify-between bg-bunker-800 text-white">
|
||||
<div className="mx-auto mb-6 w-full max-w-7xl">
|
||||
<PageHeader
|
||||
scope="instance"
|
||||
title="Environment Variables"
|
||||
description="Manage the environment variables for your Infisical instance."
|
||||
/>
|
||||
|
||||
@@ -18,6 +18,7 @@ export const GeneralPage = () => {
|
||||
<div className="container mx-auto flex flex-col justify-between bg-bunker-800 text-white">
|
||||
<div className="mx-auto mb-6 w-full max-w-7xl">
|
||||
<PageHeader
|
||||
scope="instance"
|
||||
title="General"
|
||||
description="Manage general settings for your Infisical instance."
|
||||
/>
|
||||
|
||||
@@ -16,6 +16,7 @@ export const IntegrationsPage = () => {
|
||||
<div className="container mx-auto flex flex-col justify-between bg-bunker-800 text-white">
|
||||
<div className="mx-auto mb-6 w-full max-w-7xl">
|
||||
<PageHeader
|
||||
scope="instance"
|
||||
title="Integrations"
|
||||
description="Manage integrations for your Infisical instance."
|
||||
/>
|
||||
|
||||
@@ -16,6 +16,7 @@ export const ResourceOverviewPage = () => {
|
||||
<div className="container mx-auto flex flex-col justify-between bg-bunker-800 text-white">
|
||||
<div className="mx-auto mb-6 w-full max-w-7xl">
|
||||
<PageHeader
|
||||
scope="instance"
|
||||
title="Resource Overview"
|
||||
description="Manage resources within your Infisical instance."
|
||||
/>
|
||||
|
||||
@@ -153,7 +153,7 @@ const UserPanelTable = ({
|
||||
icon={adminsOnly && <FontAwesomeIcon icon={faCheckCircle} />}
|
||||
iconPos="right"
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="flex items-center gap-x-2">
|
||||
<FontAwesomeIcon icon={faUserShield} className="text-yellow-700" />
|
||||
<span>Server Admins</span>
|
||||
</div>
|
||||
|
||||
@@ -16,6 +16,7 @@ export const AlertingPage = () => {
|
||||
</Helmet>
|
||||
<div className="mx-auto mb-6 w-full max-w-7xl">
|
||||
<PageHeader
|
||||
scope="project"
|
||||
title="Alerting"
|
||||
description="Configure alerts for expiring certificates and CAs to maintain security and compliance."
|
||||
/>
|
||||
|
||||
@@ -87,7 +87,7 @@ 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 scope="project" title={data.name}>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild className="rounded-lg">
|
||||
<div className="hover:text-primary-400 data-[state=open]:text-primary-400">
|
||||
|
||||
+1
@@ -17,6 +17,7 @@ export const CertificateAuthoritiesPage = () => {
|
||||
</Helmet>
|
||||
<div className="mx-auto mb-6 w-full max-w-7xl">
|
||||
<PageHeader
|
||||
scope="project"
|
||||
title="Certificate Authorities"
|
||||
description="Manage certificate authorities for issuing and signing certificates"
|
||||
/>
|
||||
|
||||
@@ -33,6 +33,7 @@ export const CertificatesPage = () => {
|
||||
</Helmet>
|
||||
<div className="mx-auto mb-6 w-full max-w-7xl">
|
||||
<PageHeader
|
||||
scope="project"
|
||||
title="Certificates"
|
||||
description="View and track issued certificates, monitor expiration dates, and manage certificate lifecycles."
|
||||
/>
|
||||
|
||||
@@ -45,6 +45,7 @@ export const IntegrationsListPage = () => {
|
||||
<div className="relative container mx-auto max-w-7xl pb-12 text-white">
|
||||
<div className="mb-8">
|
||||
<PageHeader
|
||||
scope="project"
|
||||
title="Integrations"
|
||||
description="Manage integrations with third-party certificate services."
|
||||
/>
|
||||
|
||||
+1
-1
@@ -73,7 +73,7 @@ export const PkiCollectionPage = () => {
|
||||
<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 scope="project" title={data.name}>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild className="rounded-lg">
|
||||
<div className="hover:text-primary-400 data-[state=open]:text-primary-400">
|
||||
|
||||
+1
-1
@@ -78,7 +78,7 @@ 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 scope="project" title={data.name}>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild className="rounded-lg">
|
||||
<div className="hover:text-primary-400 data-[state=open]:text-primary-400">
|
||||
|
||||
@@ -16,6 +16,7 @@ export const PkiSubscribersPage = () => {
|
||||
<div className="container mx-auto flex flex-col justify-between text-white">
|
||||
<div className="mx-auto mb-6 w-full max-w-7xl">
|
||||
<PageHeader
|
||||
scope="project"
|
||||
title="Subscribers"
|
||||
description="Manage subscribers that request and receive certificates, including user devices, servers, and services."
|
||||
/>
|
||||
|
||||
@@ -107,6 +107,7 @@ export const PkiTemplateListPage = () => {
|
||||
<div className="container mx-auto flex flex-col justify-between text-white">
|
||||
<div className="mx-auto mb-6 w-full max-w-7xl">
|
||||
<PageHeader
|
||||
scope="project"
|
||||
title="Certificate Templates"
|
||||
description="Manage certificate template to request and issue dynamic certificates following a strict format."
|
||||
/>
|
||||
|
||||
@@ -21,7 +21,7 @@ export const SettingsPage = () => {
|
||||
<title>{t("common.head-title", { title: t("settings.project.title") })}</title>
|
||||
</Helmet>
|
||||
<div className="w-full max-w-7xl">
|
||||
<PageHeader title={t("settings.project.title")} />
|
||||
<PageHeader scope="project" title={t("settings.project.title")} />
|
||||
<Tabs defaultValue={tabs[0].key}>
|
||||
<TabList>
|
||||
{tabs.map((tab) => (
|
||||
|
||||
@@ -18,6 +18,7 @@ export const KmipPage = () => {
|
||||
<div className="container mx-auto flex flex-col justify-between bg-bunker-800 text-white">
|
||||
<div className="mx-auto mb-6 w-full max-w-7xl">
|
||||
<PageHeader
|
||||
scope="project"
|
||||
title="KMIP"
|
||||
description="Integrate with Infisical KMS via Key Management Interoperability Protocol."
|
||||
/>
|
||||
|
||||
@@ -18,7 +18,8 @@ export const OverviewPage = () => {
|
||||
<div className="container mx-auto flex flex-col justify-between bg-bunker-800 text-white">
|
||||
<div className="mx-auto mb-6 w-full max-w-7xl">
|
||||
<PageHeader
|
||||
title="Overview Page"
|
||||
scope="project"
|
||||
title="Overview"
|
||||
description="Manage keys and perform cryptographic operations."
|
||||
/>
|
||||
<ProjectPermissionCan
|
||||
|
||||
@@ -21,7 +21,7 @@ export const SettingsPage = () => {
|
||||
<title>{t("common.head-title", { title: t("settings.project.title") })}</title>
|
||||
</Helmet>
|
||||
<div className="w-full max-w-7xl">
|
||||
<PageHeader title="Settings" />
|
||||
<PageHeader scope="project" title="Settings" />
|
||||
<Tabs defaultValue={tabs[0].key}>
|
||||
<TabList>
|
||||
{tabs.map((tab) => (
|
||||
|
||||
@@ -82,7 +82,8 @@ export const AccessManagementPage = () => {
|
||||
</Helmet>
|
||||
<div className="mx-auto mb-6 w-full max-w-7xl">
|
||||
<PageHeader
|
||||
title="Organization Access Control"
|
||||
scope="org"
|
||||
title="Access Control"
|
||||
description="Manage fine-grained access for users, groups, roles, and identities within your organization resources."
|
||||
/>
|
||||
{!currentOrg.shouldUseNewPrivilegeSystem && (
|
||||
@@ -115,7 +116,7 @@ export const AccessManagementPage = () => {
|
||||
{tabSections
|
||||
.filter((el) => !el.isHidden)
|
||||
.map((el) => (
|
||||
<Tab value={el.key} key={`org-access-tab-${el.key}`}>
|
||||
<Tab variant="org" value={el.key} key={`org-access-tab-${el.key}`}>
|
||||
{el.label}
|
||||
</Tab>
|
||||
))}
|
||||
|
||||
+1
@@ -22,6 +22,7 @@ export const AppConnectionsPage = withPermission(
|
||||
<div className="flex w-full justify-center bg-bunker-800 text-white">
|
||||
<div className="w-full max-w-7xl">
|
||||
<PageHeader
|
||||
scope="org"
|
||||
className="w-full"
|
||||
title="App Connections"
|
||||
description="Manage organization App Connections"
|
||||
|
||||
+5
@@ -31,6 +31,7 @@ import { GitLabConnectionForm } from "./GitLabConnectionForm";
|
||||
import { HCVaultConnectionForm } from "./HCVaultConnectionForm";
|
||||
import { HerokuConnectionForm } from "./HerokuAppConnectionForm";
|
||||
import { HumanitecConnectionForm } from "./HumanitecConnectionForm";
|
||||
import { LaravelForgeConnectionForm } from "./LaravelForgeConnectionForm";
|
||||
import { LdapConnectionForm } from "./LdapConnectionForm";
|
||||
import { MsSqlConnectionForm } from "./MsSqlConnectionForm";
|
||||
import { MySqlConnectionForm } from "./MySqlConnectionForm";
|
||||
@@ -146,6 +147,8 @@ const CreateForm = ({ app, onComplete, projectId }: CreateFormProps) => {
|
||||
return <HerokuConnectionForm onSubmit={onSubmit} projectId={projectId} />;
|
||||
case AppConnection.Render:
|
||||
return <RenderConnectionForm onSubmit={onSubmit} />;
|
||||
case AppConnection.LaravelForge:
|
||||
return <LaravelForgeConnectionForm onSubmit={onSubmit} />;
|
||||
case AppConnection.Flyio:
|
||||
return <FlyioConnectionForm onSubmit={onSubmit} />;
|
||||
case AppConnection.GitLab:
|
||||
@@ -297,6 +300,8 @@ const UpdateForm = ({ appConnection, onComplete }: UpdateFormProps) => {
|
||||
);
|
||||
case AppConnection.Render:
|
||||
return <RenderConnectionForm onSubmit={onSubmit} appConnection={appConnection} />;
|
||||
case AppConnection.LaravelForge:
|
||||
return <LaravelForgeConnectionForm onSubmit={onSubmit} appConnection={appConnection} />;
|
||||
case AppConnection.Flyio:
|
||||
return <FlyioConnectionForm onSubmit={onSubmit} appConnection={appConnection} />;
|
||||
case AppConnection.GitLab:
|
||||
|
||||
+136
@@ -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 {
|
||||
LaravelForgeConnectionMethod,
|
||||
TLaravelForgeConnection
|
||||
} from "@app/hooks/api/appConnections";
|
||||
import { AppConnection } from "@app/hooks/api/appConnections/enums";
|
||||
|
||||
import {
|
||||
genericAppConnectionFieldsSchema,
|
||||
GenericAppConnectionsFields
|
||||
} from "./GenericAppConnectionFields";
|
||||
|
||||
type Props = {
|
||||
appConnection?: TLaravelForgeConnection;
|
||||
onSubmit: (formData: FormData) => Promise<void>;
|
||||
};
|
||||
|
||||
const rootSchema = genericAppConnectionFieldsSchema.extend({
|
||||
app: z.literal(AppConnection.LaravelForge)
|
||||
});
|
||||
|
||||
const formSchema = z.discriminatedUnion("method", [
|
||||
rootSchema.extend({
|
||||
method: z.literal(LaravelForgeConnectionMethod.ApiToken),
|
||||
credentials: z.object({
|
||||
apiToken: z.string().trim().min(1, "API Token required")
|
||||
})
|
||||
})
|
||||
]);
|
||||
|
||||
type FormData = z.infer<typeof formSchema>;
|
||||
|
||||
export const LaravelForgeConnectionForm = ({ appConnection, onSubmit }: Props) => {
|
||||
const isUpdate = Boolean(appConnection);
|
||||
|
||||
const form = useForm<FormData>({
|
||||
resolver: zodResolver(formSchema),
|
||||
defaultValues: appConnection ?? {
|
||||
app: AppConnection.LaravelForge,
|
||||
method: LaravelForgeConnectionMethod.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 method you would like to use to connect with ${
|
||||
APP_CONNECTION_MAP[AppConnection.LaravelForge].name
|
||||
}. This field cannot be changed after creation.`}
|
||||
errorText={error?.message}
|
||||
isError={Boolean(error?.message)}
|
||||
label="Method"
|
||||
>
|
||||
<Select
|
||||
isDisabled={isUpdate}
|
||||
value={value}
|
||||
onValueChange={(val) => onChange(val)}
|
||||
className="w-full border border-mineshaft-500"
|
||||
position="popper"
|
||||
dropdownContainerClassName="max-w-none"
|
||||
>
|
||||
{Object.values(LaravelForgeConnectionMethod).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 Token"
|
||||
>
|
||||
<SecretInput
|
||||
containerClassName="text-gray-400 group-focus-within:!border-primary-400/50 border border-mineshaft-500 bg-mineshaft-900 px-2.5 py-1.5"
|
||||
value={value}
|
||||
onChange={(e) => onChange(e.target.value)}
|
||||
/>
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
<div className="mt-8 flex items-center">
|
||||
<Button
|
||||
className="mr-4"
|
||||
size="sm"
|
||||
type="submit"
|
||||
colorSchema="secondary"
|
||||
isLoading={isSubmitting}
|
||||
isDisabled={isSubmitting || !isDirty}
|
||||
>
|
||||
{isUpdate ? "Update Credentials" : "Connect to Laravel Forge"}
|
||||
</Button>
|
||||
<ModalClose asChild>
|
||||
<Button colorSchema="secondary" variant="plain">
|
||||
Cancel
|
||||
</Button>
|
||||
</ModalClose>
|
||||
</div>
|
||||
</form>
|
||||
</FormProvider>
|
||||
);
|
||||
};
|
||||
+1
-1
@@ -19,7 +19,7 @@ export const AppConnectionHeader = ({ app, isConnected, onBack }: Props) => {
|
||||
<img
|
||||
alt={`${appDetails.name} logo`}
|
||||
src={`/images/integrations/${appDetails.image}`}
|
||||
className="h-12 w-12 rounded-md bg-bunker-500 p-2"
|
||||
className="h-12 w-12 rounded-md bg-bunker-500 object-contain p-2"
|
||||
/>
|
||||
{appDetails.icon && (
|
||||
<FontAwesomeIcon
|
||||
|
||||
+6
-4
@@ -80,7 +80,7 @@ export const AppConnectionsSelect = ({ onSelect, projectType }: Props) => {
|
||||
}
|
||||
className="group relative flex h-28 cursor-pointer flex-col items-center justify-center rounded-md border border-mineshaft-600 bg-mineshaft-700 p-4 duration-200 hover:bg-mineshaft-600"
|
||||
>
|
||||
<div className="relative">
|
||||
{image && (
|
||||
<img
|
||||
src={`/images/integrations/${image}`}
|
||||
style={{
|
||||
@@ -89,14 +89,16 @@ export const AppConnectionsSelect = ({ onSelect, projectType }: Props) => {
|
||||
className="mt-auto"
|
||||
alt={`${name} logo`}
|
||||
/>
|
||||
{icon && (
|
||||
)}
|
||||
{icon && (
|
||||
<div className="relative">
|
||||
<FontAwesomeIcon
|
||||
className="absolute -right-1.5 -bottom-1.5 text-primary-700"
|
||||
size="xl"
|
||||
icon={icon}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<div className="mt-auto max-w-xs text-center text-xs font-medium text-gray-300 duration-200 group-hover:text-gray-200">
|
||||
{name}
|
||||
</div>
|
||||
|
||||
@@ -16,7 +16,8 @@ export const AuditLogsPage = () => {
|
||||
<div className="flex h-full w-full justify-center bg-bunker-800 text-white">
|
||||
<div className="w-full max-w-7xl">
|
||||
<PageHeader
|
||||
title="Audit logs"
|
||||
scope="org"
|
||||
title="Audit Logs"
|
||||
description="Audit logs for security and compliance teams to monitor information access."
|
||||
/>
|
||||
<LogsSection pageView />
|
||||
|
||||
@@ -18,8 +18,9 @@ export const BillingPage = () => {
|
||||
<meta property="og:image" content="/images/message.png" />
|
||||
</Helmet>
|
||||
<div className="flex h-full w-full justify-center bg-bunker-800 text-white">
|
||||
<div className="w-full max-w-7xl px-6">
|
||||
<div className="w-full max-w-7xl">
|
||||
<PageHeader
|
||||
scope="org"
|
||||
title={t("billing.title")}
|
||||
description="View your billing plan, next billing cycle."
|
||||
/>
|
||||
|
||||
+3
-1
@@ -25,7 +25,9 @@ export const BillingTabGroup = withPermission(
|
||||
<Tabs defaultValue={tabs[0].key}>
|
||||
<TabList>
|
||||
{tabsFiltered.map((tab) => (
|
||||
<Tab value={tab.key}>{tab.name}</Tab>
|
||||
<Tab variant="org" value={tab.key}>
|
||||
{tab.name}
|
||||
</Tab>
|
||||
))}
|
||||
</TabList>
|
||||
<TabPanel value={tabs[0].key}>
|
||||
|
||||
@@ -83,7 +83,7 @@ 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.group.name}>
|
||||
<PageHeader scope="org" title={data.group.name}>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild className="rounded-lg">
|
||||
<div className="hover:text-primary-400 data-[state=open]:text-primary-400">
|
||||
|
||||
@@ -75,7 +75,7 @@ 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.identity.name} />
|
||||
<PageHeader scope="org" title={data.identity.name} />
|
||||
<div className="flex">
|
||||
<div className="mr-4 w-96">
|
||||
<IdentityDetailsSection identityId={identityId} handlePopUpOpen={handlePopUpOpen} />
|
||||
|
||||
@@ -14,6 +14,7 @@ export const NetworkingPage = () => {
|
||||
<div className="flex w-full justify-center bg-bunker-800 text-white">
|
||||
<div className="w-full max-w-7xl">
|
||||
<PageHeader
|
||||
scope="org"
|
||||
title="Networking"
|
||||
description="Manage gateways and relays to securely access private network resources from Infisical"
|
||||
/>
|
||||
|
||||
+1
-1
@@ -22,7 +22,7 @@ export const NetworkingTabGroup = () => {
|
||||
<Tabs value={selectedTab} onValueChange={setSelectedTab}>
|
||||
<TabList>
|
||||
{tabs.map((tab) => (
|
||||
<Tab value={tab.key} key={tab.key}>
|
||||
<Tab variant="org" value={tab.key} key={tab.key}>
|
||||
{tab.name}
|
||||
</Tab>
|
||||
))}
|
||||
|
||||
@@ -65,7 +65,8 @@ export const ProjectsPage = () => {
|
||||
</Helmet>
|
||||
<div className="mb-4 flex flex-col items-start justify-start">
|
||||
<PageHeader
|
||||
title="Projects"
|
||||
scope="org"
|
||||
title="Overview"
|
||||
description="Your team's complete security toolkit - organized and ready when you need them."
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -81,6 +81,7 @@ export const Page = () => {
|
||||
{data && (
|
||||
<div className="mx-auto mb-6 w-full max-w-7xl">
|
||||
<PageHeader
|
||||
scope="org"
|
||||
title={
|
||||
<div className="flex flex-col">
|
||||
<div>
|
||||
|
||||
@@ -22,6 +22,7 @@ export const SecretSharingPage = () => {
|
||||
<div className="h-full">
|
||||
<div className="container mx-auto h-full w-full max-w-7xl bg-bunker-800 text-white">
|
||||
<PageHeader
|
||||
scope="org"
|
||||
title="Secret Sharing"
|
||||
description="Share secrets securely using a shareable link"
|
||||
>
|
||||
|
||||
+1
-1
@@ -21,7 +21,7 @@ export const SecretSharingSettingsPage = withPermission(
|
||||
</Helmet>
|
||||
<div className="flex w-full justify-center bg-bunker-800 text-white">
|
||||
<div className="w-full max-w-7xl">
|
||||
<PageHeader title="Secret Share Settings" />
|
||||
<PageHeader scope="org" title="Secret Share Settings" />
|
||||
<SecretSharingSettingsTabGroup />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -15,7 +15,7 @@ export const SettingsPage = () => {
|
||||
</Helmet>
|
||||
<div className="flex w-full justify-center bg-bunker-800 text-white">
|
||||
<div className="w-full max-w-7xl">
|
||||
<PageHeader title={t("settings.org.title")} />
|
||||
<PageHeader scope="org" title={t("settings.org.title")} />
|
||||
<OrgTabGroup />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -58,7 +58,7 @@ export const OrgTabGroup = () => {
|
||||
<Tabs value={selectedTab} onValueChange={setSelectedTab}>
|
||||
<TabList>
|
||||
{tabs.map((tab) => (
|
||||
<Tab value={tab.key} key={tab.key}>
|
||||
<Tab variant="org" value={tab.key} key={tab.key}>
|
||||
{tab.name}
|
||||
</Tab>
|
||||
))}
|
||||
|
||||
@@ -119,6 +119,7 @@ const Page = withPermission(
|
||||
{membership && (
|
||||
<div className="mx-auto mb-6 w-full max-w-7xl">
|
||||
<PageHeader
|
||||
scope="org"
|
||||
title={
|
||||
membership.user.firstName || membership.user.lastName
|
||||
? `${membership.user.firstName} ${membership.user.lastName ?? ""}`.trim()
|
||||
|
||||
@@ -23,7 +23,11 @@ export const PamAccountsPage = () => {
|
||||
<div className="h-full bg-bunker-800">
|
||||
<div className="container mx-auto flex flex-col justify-between bg-bunker-800 text-white">
|
||||
<div className="mx-auto mb-6 w-full max-w-7xl">
|
||||
<PageHeader title="Accounts" description="View, access, and manage accounts." />
|
||||
<PageHeader
|
||||
scope="project"
|
||||
title="Accounts"
|
||||
description="View, access, and manage accounts."
|
||||
/>
|
||||
<PamAccountsSection />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -24,6 +24,7 @@ export const PamResourcesPage = () => {
|
||||
<div className="container mx-auto flex flex-col justify-between bg-bunker-800 text-white">
|
||||
<div className="mx-auto mb-6 w-full max-w-7xl">
|
||||
<PageHeader
|
||||
scope="project"
|
||||
title="Resources"
|
||||
description="Manage resources such as servers, databases, and more."
|
||||
/>
|
||||
|
||||
@@ -23,6 +23,7 @@ const Page = () => {
|
||||
{session && (
|
||||
<div className="mx-auto mb-6 flex w-full max-w-7xl flex-col">
|
||||
<PageHeader
|
||||
scope="project"
|
||||
title={`${session.accountName} Session`}
|
||||
description={`View details for this ${session.accountName} session.`}
|
||||
/>
|
||||
|
||||
@@ -24,6 +24,7 @@ export const PamSessionPage = () => {
|
||||
<div className="container mx-auto flex flex-col justify-between bg-bunker-800 text-white">
|
||||
<div className="mx-auto mb-6 w-full max-w-7xl">
|
||||
<PageHeader
|
||||
scope="project"
|
||||
title="Sessions"
|
||||
description="Filter and search through account sessions."
|
||||
/>
|
||||
|
||||
@@ -13,7 +13,7 @@ export const SettingsPage = () => {
|
||||
<title>{t("common.head-title", { title: t("settings.project.title") })}</title>
|
||||
</Helmet>
|
||||
<div className="w-full max-w-7xl">
|
||||
<PageHeader title="Settings" description="Configure your PAM project." />
|
||||
<PageHeader scope="project" title="Settings" description="Configure your PAM project." />
|
||||
<Tabs defaultValue="tab-project-general">
|
||||
<TabList>
|
||||
<Tab value="tab-project-general">General</Tab>
|
||||
|
||||
@@ -40,6 +40,7 @@ const Page = () => {
|
||||
<div className="container mx-auto flex flex-col justify-between bg-bunker-800 text-white">
|
||||
<div className="mx-auto mb-6 w-full max-w-7xl">
|
||||
<PageHeader
|
||||
scope="project"
|
||||
title="Access Control"
|
||||
description="Manage fine-grained access for users, groups, roles, and identities within your project resources."
|
||||
/>
|
||||
|
||||
@@ -23,6 +23,7 @@ export const AppConnectionsPage = withProjectPermission(
|
||||
<div className="flex w-full justify-center bg-bunker-800 text-white">
|
||||
<div className="w-full max-w-7xl">
|
||||
<PageHeader
|
||||
scope="project"
|
||||
className="w-full"
|
||||
title="App Connections"
|
||||
description="Manage project App Connections"
|
||||
|
||||
@@ -16,6 +16,7 @@ export const AuditLogsPage = () => {
|
||||
<div className="flex h-full w-full justify-center bg-bunker-800 text-white">
|
||||
<div className="w-full max-w-7xl">
|
||||
<PageHeader
|
||||
scope="project"
|
||||
title="Audit logs"
|
||||
description="Audit logs for security and compliance teams to monitor information access."
|
||||
/>
|
||||
|
||||
@@ -34,7 +34,7 @@ const Page = () => {
|
||||
<div className="container mx-auto flex flex-col justify-between bg-bunker-800 text-white">
|
||||
{groupMembership ? (
|
||||
<div className="mx-auto mb-6 w-full max-w-7xl">
|
||||
<PageHeader title={groupMembership.group.name} />
|
||||
<PageHeader scope="project" title={groupMembership.group.name} />
|
||||
<div className="flex">
|
||||
<div className="mr-4 w-96">
|
||||
<GroupDetailsSection groupMembership={groupMembership} />
|
||||
|
||||
@@ -117,6 +117,7 @@ const Page = () => {
|
||||
{identityMembershipDetails ? (
|
||||
<>
|
||||
<PageHeader
|
||||
scope="project"
|
||||
title={identityMembershipDetails?.identity?.name}
|
||||
description={`Identity joined on ${identityMembershipDetails?.createdAt && formatRelative(new Date(identityMembershipDetails?.createdAt || ""), new Date())}`}
|
||||
>
|
||||
|
||||
@@ -119,6 +119,7 @@ export const Page = () => {
|
||||
{membershipDetails ? (
|
||||
<>
|
||||
<PageHeader
|
||||
scope="project"
|
||||
title={
|
||||
membershipDetails.user.firstName || membershipDetails.user.lastName
|
||||
? `${membershipDetails.user.firstName} ${membershipDetails.user.lastName}`
|
||||
|
||||
@@ -90,6 +90,7 @@ const Page = () => {
|
||||
{data && (
|
||||
<div className="mx-auto mb-6 w-full max-w-7xl">
|
||||
<PageHeader
|
||||
scope="project"
|
||||
title={
|
||||
<div className="flex flex-col">
|
||||
<div>
|
||||
|
||||
+1
@@ -260,6 +260,7 @@ export const CommitDetailsTab = ({
|
||||
Commit History
|
||||
</Button>
|
||||
<PageHeader
|
||||
scope="project"
|
||||
title={`${parsedCommitDetails.changes?.message}` || "No message"}
|
||||
description={
|
||||
<>
|
||||
|
||||
+1
@@ -307,6 +307,7 @@ export const RollbackPreviewTab = (): JSX.Element => {
|
||||
<div className="h-full w-full">
|
||||
<div>
|
||||
<PageHeader
|
||||
scope="project"
|
||||
title={`Restore folder at commit ${selectedCommitId.substring(0, 8)}`}
|
||||
description={`Will return all changes in this folder to how they appeared at the point of commit ${selectedCommitId.substring(0, 8)}. Any modifications made after this commit will be undone.`}
|
||||
/>
|
||||
|
||||
@@ -49,6 +49,7 @@ export const CommitsPage = () => {
|
||||
<div className="mx-auto flex h-full w-full max-w-7xl justify-center bg-bunker-800 py-4 text-white">
|
||||
<div className="w-full max-w-[75vw]">
|
||||
<PageHeader
|
||||
scope="project"
|
||||
title="Commits"
|
||||
description="Track, inspect, and restore your secrets and folders with confidence. View the complete history of changes made to your environment, examine specific modifications at each commit point, and preview the exact impact before rolling back to previous states."
|
||||
/>
|
||||
|
||||
+1
@@ -94,6 +94,7 @@ export const IntegrationDetailsByIDPage = () => {
|
||||
{integration ? (
|
||||
<div className="mx-auto mb-6 w-full max-w-7xl">
|
||||
<PageHeader
|
||||
scope="project"
|
||||
title={`${integrationSlugNameMapping[integration.integration]} Integration`}
|
||||
>
|
||||
<DropdownMenu>
|
||||
|
||||
@@ -48,6 +48,7 @@ export const IntegrationsListPage = () => {
|
||||
<div className="relative container mx-auto max-w-7xl pb-12 text-white">
|
||||
<div className="mb-8">
|
||||
<PageHeader
|
||||
scope="project"
|
||||
title="Integrations"
|
||||
description="Manage integrations with third-party services."
|
||||
/>
|
||||
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
import { TLaravelForgeSync } from "@app/hooks/api/secretSyncs/types/laravel-forge-sync";
|
||||
|
||||
import { getSecretSyncDestinationColValues } from "../helpers";
|
||||
import { SecretSyncTableCell } from "../SecretSyncTableCell";
|
||||
|
||||
type Props = {
|
||||
secretSync: TLaravelForgeSync;
|
||||
};
|
||||
|
||||
export const LaravelForgeSyncDestinationCol = ({ secretSync }: Props) => {
|
||||
const { primaryText, secondaryText } = getSecretSyncDestinationColValues(secretSync);
|
||||
|
||||
return <SecretSyncTableCell primaryText={primaryText} secondaryText={secondaryText} />;
|
||||
};
|
||||
+3
@@ -20,6 +20,7 @@ import { GitLabSyncDestinationCol } from "./GitLabSyncDestinationCol";
|
||||
import { HCVaultSyncDestinationCol } from "./HCVaultSyncDestinationCol";
|
||||
import { HerokuSyncDestinationCol } from "./HerokuSyncDestinationCol";
|
||||
import { HumanitecSyncDestinationCol } from "./HumanitecSyncDestinationCol";
|
||||
import { LaravelForgeSyncDestinationCol } from "./LaravelForgeSyncDestinationCol";
|
||||
import { NetlifySyncDestinationCol } from "./NetlifySyncDestinationCol";
|
||||
import { OCIVaultSyncDestinationCol } from "./OCIVaultSyncDestinationCol";
|
||||
import { RailwaySyncDestinationCol } from "./RailwaySyncDestinationCol";
|
||||
@@ -97,6 +98,8 @@ export const SecretSyncDestinationCol = ({ secretSync }: Props) => {
|
||||
return <NetlifySyncDestinationCol secretSync={secretSync} />;
|
||||
case SecretSync.Bitbucket:
|
||||
return <BitbucketSyncDestinationCol secretSync={secretSync} />;
|
||||
case SecretSync.LaravelForge:
|
||||
return <LaravelForgeSyncDestinationCol secretSync={secretSync} />;
|
||||
default:
|
||||
throw new Error(
|
||||
`Unhandled Secret Sync Destination Col: ${(secretSync as TSecretSync).destination}`
|
||||
|
||||
+4
@@ -194,6 +194,10 @@ export const getSecretSyncDestinationColValues = (secretSync: TSecretSync) => {
|
||||
primaryText = destinationConfig.workspaceSlug;
|
||||
secondaryText = destinationConfig.repositorySlug;
|
||||
break;
|
||||
case SecretSync.LaravelForge:
|
||||
primaryText = destinationConfig.siteName || destinationConfig.siteId;
|
||||
secondaryText = destinationConfig.orgName || destinationConfig.orgSlug;
|
||||
break;
|
||||
default:
|
||||
throw new Error(`Unhandled Destination Col Values ${destination}`);
|
||||
}
|
||||
|
||||
@@ -914,6 +914,7 @@ export const OverviewPage = () => {
|
||||
<div className="relative mx-auto max-w-7xl text-mineshaft-50 dark:scheme-dark">
|
||||
<div className="flex w-full items-baseline justify-between">
|
||||
<PageHeader
|
||||
scope="project"
|
||||
title="Secrets Overview"
|
||||
description={
|
||||
<p className="text-md text-bunker-300">
|
||||
|
||||
+18
-15
@@ -82,6 +82,7 @@ type Props = {
|
||||
isImported: boolean;
|
||||
}[];
|
||||
}[];
|
||||
isSecretPresent?: boolean;
|
||||
};
|
||||
|
||||
export const SecretEditRow = ({
|
||||
@@ -101,7 +102,8 @@ export const SecretEditRow = ({
|
||||
isRotatedSecret,
|
||||
importedBy,
|
||||
importedSecret,
|
||||
isEmpty
|
||||
isEmpty,
|
||||
isSecretPresent
|
||||
}: Props) => {
|
||||
const { handlePopUpOpen, handlePopUpToggle, handlePopUpClose, popUp } = usePopUp([
|
||||
"editSecret"
|
||||
@@ -113,20 +115,21 @@ export const SecretEditRow = ({
|
||||
|
||||
const [isFieldFocused, setIsFieldFocused] = useToggle();
|
||||
|
||||
const fetchSecretValueParams = importedSecret
|
||||
? {
|
||||
environment: importedSecret.environment,
|
||||
secretPath: importedSecret.secretPath,
|
||||
secretKey: importedSecret.secret?.key ?? "",
|
||||
projectId: currentProject.id
|
||||
}
|
||||
: {
|
||||
environment,
|
||||
secretPath,
|
||||
secretKey: secretName,
|
||||
projectId: currentProject.id,
|
||||
isOverride
|
||||
};
|
||||
const fetchSecretValueParams =
|
||||
importedSecret && !isSecretPresent
|
||||
? {
|
||||
environment: importedSecret.environment,
|
||||
secretPath: importedSecret.secretPath,
|
||||
secretKey: importedSecret.secret?.key ?? "",
|
||||
projectId: currentProject.id
|
||||
}
|
||||
: {
|
||||
environment,
|
||||
secretPath,
|
||||
secretKey: secretName,
|
||||
projectId: currentProject.id,
|
||||
isOverride
|
||||
};
|
||||
|
||||
// scott: only fetch value if secret exists, has non-empty value and user has permission
|
||||
const canFetchValue = Boolean(importedSecret ?? secretId) && !isEmpty && !secretValueHidden;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user