mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-06 10:28:22 +00:00
feat: added default product switch in project settings
This commit is contained in:
@@ -5,12 +5,14 @@ import { z } from "zod";
|
|||||||
|
|
||||||
import { createNotification } from "@app/components/notifications";
|
import { createNotification } from "@app/components/notifications";
|
||||||
import { ProjectPermissionCan } from "@app/components/permissions";
|
import { ProjectPermissionCan } from "@app/components/permissions";
|
||||||
import { Button, FormControl, Input, TextArea } from "@app/components/v2";
|
import { Button, FormControl, Input, Select, SelectItem, TextArea } from "@app/components/v2";
|
||||||
import { ProjectPermissionActions, ProjectPermissionSub, useWorkspace } from "@app/context";
|
import { ProjectPermissionActions, ProjectPermissionSub, useWorkspace } from "@app/context";
|
||||||
import { useUpdateProject } from "@app/hooks/api";
|
import { useUpdateProject } from "@app/hooks/api";
|
||||||
|
import { ProjectType } from "@app/hooks/api/workspace/types";
|
||||||
|
|
||||||
const baseFormSchema = z.object({
|
const baseFormSchema = z.object({
|
||||||
name: z.string().min(1, "Required").max(64, "Too long, maximum length is 64 characters"),
|
name: z.string().min(1, "Required").max(64, "Too long, maximum length is 64 characters"),
|
||||||
|
defaultProduct: z.nativeEnum(ProjectType).default(ProjectType.SecretManager),
|
||||||
description: z
|
description: z
|
||||||
.string()
|
.string()
|
||||||
.trim()
|
.trim()
|
||||||
@@ -50,6 +52,7 @@ export const ProjectOverviewChangeSection = ({ showSlugField = false }: Props) =
|
|||||||
reset({
|
reset({
|
||||||
name: currentWorkspace.name,
|
name: currentWorkspace.name,
|
||||||
description: currentWorkspace.description ?? "",
|
description: currentWorkspace.description ?? "",
|
||||||
|
defaultProduct: currentWorkspace.defaultProduct,
|
||||||
...(showSlugField && { slug: currentWorkspace.slug })
|
...(showSlugField && { slug: currentWorkspace.slug })
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -63,6 +66,7 @@ export const ProjectOverviewChangeSection = ({ showSlugField = false }: Props) =
|
|||||||
projectID: currentWorkspace.id,
|
projectID: currentWorkspace.id,
|
||||||
newProjectName: data.name,
|
newProjectName: data.name,
|
||||||
newProjectDescription: data.description,
|
newProjectDescription: data.description,
|
||||||
|
defaultProduct: data.defaultProduct,
|
||||||
...(showSlugField &&
|
...(showSlugField &&
|
||||||
"slug" in data && {
|
"slug" in data && {
|
||||||
newSlug: data.slug !== currentWorkspace.slug ? data.slug : undefined
|
newSlug: data.slug !== currentWorkspace.slug ? data.slug : undefined
|
||||||
@@ -212,6 +216,31 @@ export const ProjectOverviewChangeSection = ({ showSlugField = false }: Props) =
|
|||||||
</ProjectPermissionCan>
|
</ProjectPermissionCan>
|
||||||
</div>
|
</div>
|
||||||
</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>
|
<div>
|
||||||
<ProjectPermissionCan
|
<ProjectPermissionCan
|
||||||
I={ProjectPermissionActions.Edit}
|
I={ProjectPermissionActions.Edit}
|
||||||
|
|||||||
@@ -120,7 +120,10 @@ const NewProjectForm = ({ onOpenChange }: NewProjectFormProps) => {
|
|||||||
createNotification({ text: "Project created", type: "success" });
|
createNotification({ text: "Project created", type: "success" });
|
||||||
reset();
|
reset();
|
||||||
onOpenChange(false);
|
onOpenChange(false);
|
||||||
navigate({ to: getProjectHomePage(project.defaultType), params: { projectId: project.id } });
|
navigate({
|
||||||
|
to: getProjectHomePage(project.defaultProduct),
|
||||||
|
params: { projectId: project.id }
|
||||||
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
createNotification({ text: "Failed to create project", type: "error" });
|
createNotification({ text: "Failed to create project", type: "error" });
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/* eslint-disable react/prop-types */
|
/* eslint-disable react/prop-types */
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { faEllipsis, faSlash, faSort } from "@fortawesome/free-solid-svg-icons";
|
import { faEllipsis, faSort } from "@fortawesome/free-solid-svg-icons";
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
import { Link, ReactNode } from "@tanstack/react-router";
|
import { Link, ReactNode } from "@tanstack/react-router";
|
||||||
import { LinkComponentProps } from "node_modules/@tanstack/react-router/dist/esm/link";
|
import { LinkComponentProps } from "node_modules/@tanstack/react-router/dist/esm/link";
|
||||||
@@ -40,7 +40,7 @@ const BreadcrumbItem = React.forwardRef<HTMLLIElement, React.ComponentPropsWitho
|
|||||||
({ className, ...props }, ref) => (
|
({ className, ...props }, ref) => (
|
||||||
<li
|
<li
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={twMerge("inline-flex text-sm items-center gap-1.5", className)}
|
className={twMerge("inline-flex items-center gap-1.5 text-sm", className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
@@ -79,9 +79,7 @@ BreadcrumbPage.displayName = "BreadcrumbPage";
|
|||||||
|
|
||||||
const BreadcrumbSeparator = ({ children, className, ...props }: React.ComponentProps<"li">) => (
|
const BreadcrumbSeparator = ({ children, className, ...props }: React.ComponentProps<"li">) => (
|
||||||
<li role="presentation" aria-hidden="true" className={twMerge("", className)} {...props}>
|
<li role="presentation" aria-hidden="true" className={twMerge("", className)} {...props}>
|
||||||
{children ?? (
|
{children ?? <p className="px-2 text-lg text-mineshaft-400/70">/</p>}
|
||||||
<p className="text-lg text-mineshaft-400/70 px-2">/</p>
|
|
||||||
)}
|
|
||||||
</li>
|
</li>
|
||||||
);
|
);
|
||||||
BreadcrumbSeparator.displayName = "BreadcrumbSeparator";
|
BreadcrumbSeparator.displayName = "BreadcrumbSeparator";
|
||||||
@@ -136,7 +134,7 @@ const BreadcrumbContainer = ({ breadcrumbs }: { breadcrumbs: TBreadcrumbFormat[]
|
|||||||
<DropdownMenu>
|
<DropdownMenu>
|
||||||
<DropdownMenuTrigger>
|
<DropdownMenuTrigger>
|
||||||
<BreadcrumbItem>
|
<BreadcrumbItem>
|
||||||
<BreadcrumbSegment className="px-2 py-1 rounded-md py-2 hover:bg-mineshaft-600">
|
<BreadcrumbSegment className="rounded-md px-2 py-1 py-2 hover:bg-mineshaft-600">
|
||||||
{el.label} <FontAwesomeIcon icon={faSort} size="sm" />
|
{el.label} <FontAwesomeIcon icon={faSort} size="sm" />
|
||||||
</BreadcrumbSegment>
|
</BreadcrumbSegment>
|
||||||
</BreadcrumbItem>
|
</BreadcrumbItem>
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ export type IdentityMembershipOrg = {
|
|||||||
export type IdentityMembership = {
|
export type IdentityMembership = {
|
||||||
id: string;
|
id: string;
|
||||||
identity: Identity;
|
identity: Identity;
|
||||||
project: Pick<Workspace, "id" | "name" | "defaultType">;
|
project: Pick<Workspace, "id" | "name" | "defaultProduct">;
|
||||||
roles: Array<
|
roles: Array<
|
||||||
{
|
{
|
||||||
id: string;
|
id: string;
|
||||||
|
|||||||
@@ -280,7 +280,8 @@ export const useUpdateProject = () => {
|
|||||||
newProjectDescription,
|
newProjectDescription,
|
||||||
newSlug,
|
newSlug,
|
||||||
secretSharing,
|
secretSharing,
|
||||||
showSnapshotsLegacy
|
showSnapshotsLegacy,
|
||||||
|
defaultProduct
|
||||||
}) => {
|
}) => {
|
||||||
const { data } = await apiRequest.patch<{ workspace: Workspace }>(
|
const { data } = await apiRequest.patch<{ workspace: Workspace }>(
|
||||||
`/api/v1/workspace/${projectID}`,
|
`/api/v1/workspace/${projectID}`,
|
||||||
@@ -288,6 +289,7 @@ export const useUpdateProject = () => {
|
|||||||
name: newProjectName,
|
name: newProjectName,
|
||||||
description: newProjectDescription,
|
description: newProjectDescription,
|
||||||
slug: newSlug,
|
slug: newSlug,
|
||||||
|
defaultProduct,
|
||||||
secretSharing,
|
secretSharing,
|
||||||
showSnapshotsLegacy
|
showSnapshotsLegacy
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ export type Workspace = {
|
|||||||
__v: number;
|
__v: number;
|
||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
defaultType: ProjectType;
|
defaultProduct: ProjectType;
|
||||||
description?: string;
|
description?: string;
|
||||||
orgId: string;
|
orgId: string;
|
||||||
version: ProjectVersion;
|
version: ProjectVersion;
|
||||||
@@ -80,6 +80,7 @@ export type UpdateProjectDTO = {
|
|||||||
newSlug?: string;
|
newSlug?: string;
|
||||||
secretSharing?: boolean;
|
secretSharing?: boolean;
|
||||||
showSnapshotsLegacy?: boolean;
|
showSnapshotsLegacy?: boolean;
|
||||||
|
defaultProduct?: ProjectType;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type UpdatePitVersionLimitDTO = { projectSlug: string; pitVersionLimit: number };
|
export type UpdatePitVersionLimitDTO = { projectSlug: string; pitVersionLimit: number };
|
||||||
|
|||||||
@@ -168,11 +168,10 @@ export const Navbar = () => {
|
|||||||
<p className="pl-2 pr-3 text-lg text-mineshaft-400/70">/</p>
|
<p className="pl-2 pr-3 text-lg text-mineshaft-400/70">/</p>
|
||||||
<DropdownMenu modal={false}>
|
<DropdownMenu modal={false}>
|
||||||
<Link to="/organization/projects">
|
<Link to="/organization/projects">
|
||||||
<div className="flex cursor-pointer items-center gap-2 text-sm text-white">
|
<div className="flex cursor-pointer items-center gap-2 text-sm text-white transition-all duration-100 hover:text-primary-400">
|
||||||
<div>
|
<div>
|
||||||
<FontAwesomeIcon icon={faCubes} className="pr-1 text-xs" />
|
<FontAwesomeIcon icon={faCubes} className="pr-1 text-xs" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="max-w-32 overflow-hidden text-ellipsis">{currentOrg?.name}</div>
|
<div className="max-w-32 overflow-hidden text-ellipsis">{currentOrg?.name}</div>
|
||||||
<div className="mr-2 rounded border border-mineshaft-500 px-1 text-xs text-bunker-300">
|
<div className="mr-2 rounded border border-mineshaft-500 px-1 text-xs text-bunker-300">
|
||||||
{getPlan(subscription)}
|
{getPlan(subscription)}
|
||||||
|
|||||||
@@ -102,13 +102,13 @@ export const ProjectSelect = () => {
|
|||||||
<DropdownMenu modal={false}>
|
<DropdownMenu modal={false}>
|
||||||
<Link
|
<Link
|
||||||
to={getProjectHomePage(
|
to={getProjectHomePage(
|
||||||
getCurrentProductFromUrl(window.location.href) || ProjectType.SecretManager
|
getCurrentProductFromUrl(window.location.href) || currentWorkspace.defaultProduct
|
||||||
)}
|
)}
|
||||||
params={{
|
params={{
|
||||||
projectId: currentWorkspace.id
|
projectId: currentWorkspace.id
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div className="flex cursor-pointer items-center gap-2 text-sm text-white">
|
<div className="flex cursor-pointer items-center gap-2 text-sm text-white duration-100 hover:text-primary-400">
|
||||||
<div>
|
<div>
|
||||||
<FontAwesomeIcon icon={faCube} className="text-xs" />
|
<FontAwesomeIcon icon={faCube} className="text-xs" />
|
||||||
</div>
|
</div>
|
||||||
@@ -156,7 +156,7 @@ export const ProjectSelect = () => {
|
|||||||
// to reproduce change this back to router.push and switch between two projects with different env count
|
// to reproduce change this back to router.push and switch between two projects with different env count
|
||||||
// look into this on dashboard revamp
|
// look into this on dashboard revamp
|
||||||
const url = linkOptions({
|
const url = linkOptions({
|
||||||
to: getProjectHomePage(workspace.defaultType),
|
to: getProjectHomePage(workspace.defaultProduct),
|
||||||
params: {
|
params: {
|
||||||
projectId: workspace.id
|
projectId: workspace.id
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-2
@@ -105,7 +105,7 @@ export const OrgAdminProjects = withPermission(
|
|||||||
<TBody>
|
<TBody>
|
||||||
{isProjectsLoading && <TableSkeleton columns={4} innerKey="projects" />}
|
{isProjectsLoading && <TableSkeleton columns={4} innerKey="projects" />}
|
||||||
{!isProjectsLoading &&
|
{!isProjectsLoading &&
|
||||||
projects?.map(({ name, slug, createdAt, id, defaultType }) => (
|
projects?.map(({ name, slug, createdAt, id, defaultProduct }) => (
|
||||||
<Tr key={`project-${id}`} className="group w-full">
|
<Tr key={`project-${id}`} className="group w-full">
|
||||||
<Td>{name}</Td>
|
<Td>{name}</Td>
|
||||||
<Td>{slug}</Td>
|
<Td>{slug}</Td>
|
||||||
@@ -126,7 +126,7 @@ export const OrgAdminProjects = withPermission(
|
|||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
handleAccessProject(defaultType, id);
|
handleAccessProject(defaultProduct, id);
|
||||||
}}
|
}}
|
||||||
icon={<FontAwesomeIcon icon={faSignIn} />}
|
icon={<FontAwesomeIcon icon={faSignIn} />}
|
||||||
disabled={
|
disabled={
|
||||||
|
|||||||
@@ -418,10 +418,10 @@ export const LogsFilter = ({
|
|||||||
onChange(e);
|
onChange(e);
|
||||||
}}
|
}}
|
||||||
placeholder="All projects"
|
placeholder="All projects"
|
||||||
options={workspacesInOrg.map(({ name, id, defaultType }) => ({
|
options={workspacesInOrg.map(({ name, id, defaultProduct }) => ({
|
||||||
name,
|
name,
|
||||||
id,
|
id,
|
||||||
type: defaultType
|
type: defaultProduct
|
||||||
}))}
|
}))}
|
||||||
getOptionValue={(option) => option.id}
|
getOptionValue={(option) => option.id}
|
||||||
getOptionLabel={(option) => option.name}
|
getOptionLabel={(option) => option.name}
|
||||||
|
|||||||
@@ -222,7 +222,7 @@ export const AllProjectView = ({
|
|||||||
onKeyDown={(evt) => {
|
onKeyDown={(evt) => {
|
||||||
if (evt.key === "Enter" && workspace.isMember) {
|
if (evt.key === "Enter" && workspace.isMember) {
|
||||||
navigate({
|
navigate({
|
||||||
to: getProjectHomePage(workspace.defaultType),
|
to: getProjectHomePage(workspace.defaultProduct),
|
||||||
params: {
|
params: {
|
||||||
projectId: workspace.id
|
projectId: workspace.id
|
||||||
}
|
}
|
||||||
@@ -232,7 +232,7 @@ export const AllProjectView = ({
|
|||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (workspace.isMember) {
|
if (workspace.isMember) {
|
||||||
navigate({
|
navigate({
|
||||||
to: getProjectHomePage(workspace.defaultType),
|
to: getProjectHomePage(workspace.defaultProduct),
|
||||||
params: {
|
params: {
|
||||||
projectId: workspace.id
|
projectId: workspace.id
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -153,7 +153,7 @@ export const MyProjectView = ({
|
|||||||
<div
|
<div
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
navigate({
|
navigate({
|
||||||
to: getProjectHomePage(workspace.defaultType),
|
to: getProjectHomePage(workspace.defaultProduct),
|
||||||
params: {
|
params: {
|
||||||
projectId: workspace.id
|
projectId: workspace.id
|
||||||
}
|
}
|
||||||
@@ -215,7 +215,7 @@ export const MyProjectView = ({
|
|||||||
<div
|
<div
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
navigate({
|
navigate({
|
||||||
to: getProjectHomePage(workspace.defaultType),
|
to: getProjectHomePage(workspace.defaultProduct),
|
||||||
params: {
|
params: {
|
||||||
projectId: workspace.id
|
projectId: workspace.id
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user