feat: resolved a lot of ts issues, planning to migrate to v5 tanstack query

This commit is contained in:
=
2024-12-17 16:42:20 +05:30
parent 701eb7cfc6
commit cb22ee315e
67 changed files with 803 additions and 1202 deletions

View File

@@ -42,9 +42,10 @@
"@radix-ui/react-toast": "^1.2.3",
"@radix-ui/react-tooltip": "^1.1.5",
"@sindresorhus/slugify": "^2.2.1",
"@tanstack/react-query": "^4.36.1",
"@tanstack/react-query": "^5.62.7",
"@tanstack/react-router": "^1.87.9",
"@tanstack/virtual-file-routes": "^1.87.6",
"@tanstack/zod-adapter": "^1.91.0",
"@types/nprogress": "^0.2.3",
"@ucast/mongo2js": "^1.3.4",
"argon2-browser": "^1.18.0",
@@ -3628,9 +3629,9 @@
}
},
"node_modules/@tanstack/query-core": {
"version": "4.36.1",
"resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-4.36.1.tgz",
"integrity": "sha512-DJSilV5+ytBP1FbFcEJovv4rnnm/CokuVvrBEtW/Va9DvuJ3HksbXUJEpI0aV1KtuL4ZoO9AVE6PyNLzF7tLeA==",
"version": "5.62.7",
"resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.62.7.tgz",
"integrity": "sha512-fgpfmwatsrUal6V+8EC2cxZIQVl9xvL7qYa03gsdsCy985UTUlS4N+/3hCzwR0PclYDqisca2AqR1BVgJGpUDA==",
"license": "MIT",
"funding": {
"type": "github",
@@ -3638,30 +3639,19 @@
}
},
"node_modules/@tanstack/react-query": {
"version": "4.36.1",
"resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-4.36.1.tgz",
"integrity": "sha512-y7ySVHFyyQblPl3J3eQBWpXZkliroki3ARnBKsdJchlgt7yJLRDUcf4B8soufgiYt3pEQIkBWBx1N9/ZPIeUWw==",
"version": "5.62.7",
"resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.62.7.tgz",
"integrity": "sha512-+xCtP4UAFDTlRTYyEjLx0sRtWyr5GIk7TZjZwBu4YaNahi3Rt2oMyRqfpfVrtwsqY2sayP4iXVCwmC+ZqqFmuw==",
"license": "MIT",
"dependencies": {
"@tanstack/query-core": "4.36.1",
"use-sync-external-store": "^1.2.0"
"@tanstack/query-core": "5.62.7"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/tannerlinsley"
},
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0 || ^18.0.0",
"react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0",
"react-native": "*"
},
"peerDependenciesMeta": {
"react-dom": {
"optional": true
},
"react-native": {
"optional": true
}
"react": "^18 || ^19"
}
},
"node_modules/@tanstack/react-router": {
@@ -3855,6 +3845,23 @@
"url": "https://github.com/sponsors/tannerlinsley"
}
},
"node_modules/@tanstack/zod-adapter": {
"version": "1.91.0",
"resolved": "https://registry.npmjs.org/@tanstack/zod-adapter/-/zod-adapter-1.91.0.tgz",
"integrity": "sha512-OccU1RwZ7svIyztcWrVreurWUwaTv98wAWTjzAOS6fe8Ajl9oLT9+mvj4vdi5gn5ycKyu3xkrIPhLIp5fGLZcQ==",
"license": "MIT",
"engines": {
"node": ">=12"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/tannerlinsley"
},
"peerDependencies": {
"@tanstack/react-router": ">=1.43.2",
"zod": "^3.23.8"
}
},
"node_modules/@types/argon2-browser": {
"version": "1.18.4",
"resolved": "https://registry.npmjs.org/@types/argon2-browser/-/argon2-browser-1.18.4.tgz",

View File

@@ -46,9 +46,10 @@
"@radix-ui/react-toast": "^1.2.3",
"@radix-ui/react-tooltip": "^1.1.5",
"@sindresorhus/slugify": "^2.2.1",
"@tanstack/react-query": "^4.36.1",
"@tanstack/react-query": "^5.62.7",
"@tanstack/react-router": "^1.87.9",
"@tanstack/virtual-file-routes": "^1.87.6",
"@tanstack/zod-adapter": "^1.91.0",
"@types/nprogress": "^0.2.3",
"@ucast/mongo2js": "^1.3.4",
"argon2-browser": "^1.18.0",

View File

@@ -90,7 +90,12 @@ export default function NavHeader({
)}
<FontAwesomeIcon icon={faAngleRight} className="ml-3 mr-3 text-sm text-gray-400" />
{pageName === "Secrets" ? (
<Link to={{ pathname: "/project/[id]/secrets/overview", query: { id: navigate.query.id } }}>
<Link
to={{
pathname: "/project/$projectId/secrets/overview",
params: { id: currentWorkspace?.id }
}}
>
<a className="text-sm font-semibold text-primary/80 hover:text-primary">{pageName}</a>
</Link>
) : (

View File

@@ -1,13 +1,13 @@
import { FC } from "react";
import { Controller, useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { useNavigate } from "@tanstack/react-router";
import z from "zod";
import { createNotification } from "@app/components/notifications";
import { Button, FormControl, Input, Modal, ModalContent } from "@app/components/v2";
import { useCreateOrg, useSelectOrganization } from "@app/hooks/api";
import { ProjectType } from "@app/hooks/api/workspace/types";
import { useNavigate } from "@tanstack/react-router";
const schema = z
.object({
@@ -58,7 +58,7 @@ export const CreateOrgModal: FC<CreateOrgModalProps> = ({ isOpen, onClose }) =>
});
navigate({
to: `/organization/$organizationId/${ProjectType.SecretManager}` as const,
to: `/organization/$organizationId/${ProjectType.SecretManager}/overview` as const,
params: {
organizationId: organization.id
}

View File

@@ -38,7 +38,7 @@ const attemptChangePassword = ({ email, currentPassword, newPassword }: Params):
const clientProof = clientOldPassword.getProof();
clientNewPassword.init({ username: email, password: newPassword }, async () => {
clientNewPassword.createVerifier(async (err, result) => {
clientNewPassword.createVerifier(async (_err, result) => {
try {
const derivedKey = await deriveArgonKey({
password: newPassword,

View File

@@ -1,63 +0,0 @@
/* eslint-disable prefer-template */
/* eslint-disable prefer-rest-params */
/* eslint-disable @typescript-eslint/no-unused-expressions */
/* eslint-disable no-nested-ternary */
/* eslint-disable no-unexpected-multiline */
/* eslint-disable react-hooks/exhaustive-deps */
/* eslint-disable vars-on-top */
/* eslint-disable no-var */
/* eslint-disable func-names */
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-nocheck
import { INTERCOMid as APPid } from "@app/components/utilities/config";
// Loads Intercom with the snippet
// This must be run before boot, it initializes window.Intercom
// prettier-ignore
export const load = () => {
(function(){
var w=window;
var ic=w.Intercom;
if(typeof ic==="function"){
ic("reattach_activator");
ic("update",w.intercomSettings);
} else {
var d=document;
var i=function() {
i.c(arguments);
};
i.q=[];
i.c=function(args) {
i.q.push(args);
};
w.Intercom=i;
var l=function() {
var s=d.createElement("script");
s.type="text/javascript";
s.async=true;
s.src="https://widget.intercom.io/widget/" + APPid;
var x=d.getElementsByTagName("script")[0];
x.parentNode.insertBefore(s, x);
};
if (document.readyState==="complete") {
l();
} else if (w.attachEvent) {
w.attachEvent("onload",l);
} else {
w.addEventListener("load",l,false);
}
}
})();
}
// Initializes Intercom
export const boot = (options = {}) => {
window && window.Intercom && window.Intercom("boot", { appid: APPid, ...options });
};
export const update = () => {
window && window.Intercom && window.Intercom("update");
};

View File

@@ -1,40 +0,0 @@
import { useEffect } from "react";
import { useRouter } from "next/router";
import { useUser } from "@app/context";
import { boot as bootIntercom, load as loadIntercom, update as updateIntercom } from "./intercom";
// eslint-disable-next-line @typescript-eslint/no-unused-vars
export const IntercomProvider = ({ children }: { children: any }) => {
const { user } = useUser();
const router = useRouter();
if (typeof window !== "undefined") {
loadIntercom();
bootIntercom({
name: `${user?.firstName || ""} ${user?.lastName || ""}`,
email: user?.email || "",
created_at: Math.floor((new Date(user?.createdAt)?.getTime() || 0) / 1000)
});
}
useEffect(() => {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const handleRouteChange = (url: string) => {
if (typeof window !== "undefined") {
updateIntercom();
}
};
router.events.on("routeChangeStart", handleRouteChange);
// If the component is unmounted, unsubscribe
// from the event with the `off` method:
return () => {
router.events.off("routeChangeStart", handleRouteChange);
};
}, [router.events]);
return children;
};

View File

@@ -39,7 +39,7 @@ export const SecretPathInput = ({
const workspaceId = currentWorkspace?.id || "";
const { folderNames: folders } = useGetFoldersByEnv({
path: secretPath,
environments: [environment || currentWorkspace?.environments?.[0].slug],
environments: [environment || currentWorkspace?.environments?.[0].slug || ""],
projectId: workspaceId
});

View File

@@ -1,54 +1,5 @@
import { createContext, ReactNode } from "react";
import { useRouteContext } from "@tanstack/react-router";
import { useGetUserOrgPermissions } from "@app/hooks/api";
import { OrgUser } from "@app/hooks/api/types";
import { useOrganization } from "../OrganizationContext";
import { TOrgPermission } from "./types";
type Props = {
children: ReactNode;
};
const OrgPermissionContext = createContext<null | {
permission: TOrgPermission;
membership: OrgUser | null;
}>(null);
export const OrgPermissionProvider = ({ children }: Props): JSX.Element => {
const { currentOrg } = useOrganization();
const orgId = currentOrg?.id || "";
const { data: permission, isLoading } = useGetUserOrgPermissions({ orgId });
if (isLoading) {
return (
<div className="flex h-screen w-screen items-center justify-center bg-bunker-800">
<img
src="/images/loading/loading.gif"
height={70}
width={120}
decoding="async"
loading="lazy"
alt="infisical loading indicator"
/>
</div>
);
}
if (!permission) {
return (
<div className="flex h-screen w-screen items-center justify-center bg-bunker-800">
Failed to load user permissions
</div>
);
}
return (
<OrgPermissionContext.Provider value={permission}>{children}</OrgPermissionContext.Provider>
);
};
export const useOrgPermission = () => {
const ctx = useRouteContext({
from: "/_authenticate/_org_details",

View File

@@ -1,3 +1,3 @@
export { OrgPermissionProvider, useOrgPermission } from "./OrgPermissionContext";
export { useOrgPermission } from "./OrgPermissionContext";
export type { TOrgPermission } from "./types";
export { OrgPermissionActions, OrgPermissionSubjects } from "./types";

View File

@@ -1,40 +1,5 @@
import { createContext, ReactNode, useContext, useMemo } from "react";
import { useRouteContext } from "@tanstack/react-router";
import { useGetOrganizations } from "@app/hooks/api";
import { Organization } from "@app/hooks/api/types";
type TOrgContext = {
orgs?: Organization[];
currentOrg?: Organization;
isLoading: boolean;
};
const OrgContext = createContext<TOrgContext | null>(null);
type Props = {
children: ReactNode;
};
export const OrgProvider = ({ children }: Props): JSX.Element => {
const { data: userOrgs, isLoading } = useGetOrganizations();
// const currentWsOrgID = currentWorkspace?.organization;
const currentWsOrgID = localStorage.getItem("orgData.id");
// memorize the workspace details for the context
const value = useMemo<TOrgContext>(
() => ({
orgs: userOrgs,
currentOrg: (userOrgs || []).find(({ id }) => id === currentWsOrgID),
isLoading
}),
[currentWsOrgID, userOrgs, isLoading]
);
return <OrgContext.Provider value={value}>{children}</OrgContext.Provider>;
};
export const useOrganization = () => {
const currentOrg = useRouteContext({
from: "/_authenticate/_org_details",

View File

@@ -1 +1 @@
export { OrgProvider, useOrganization } from "./OrganizationContext";
export { useOrganization } from "./OrganizationContext";

View File

@@ -1,86 +1,11 @@
import { createContext, ReactNode, useEffect, useMemo } from "react";
import { Helmet } from "react-helmet";
import { useNavigate, useRouteContext } from "@tanstack/react-router";
import { ContentLoader } from "@app/components/v2/ContentLoader";
import { useGetServerConfig } from "@app/hooks/api";
import { TServerConfig } from "@app/hooks/api/admin/types";
type TServerConfigContext = {
config: TServerConfig;
};
const ServerConfigContext = createContext<TServerConfigContext | null>(null);
type Props = {
children: ReactNode;
};
export const ServerConfigProvider = ({ children }: Props): JSX.Element => {
const navigate = useNavigate();
const { data, isLoading } = useGetServerConfig();
// memorize the workspace details for the context
const value = useMemo<TServerConfigContext>(() => {
return {
config: data!
};
}, [data]);
useEffect(() => {
if (!isLoading && data && !data.initialized && !data.isMigrationModeOn) {
navigate({ to: "/admin/signup" });
}
}, [isLoading, data]);
if (!isLoading && data?.isMigrationModeOn) {
return (
<div className="relative mx-auto flex h-screen w-full flex-col items-center justify-center space-y-8 bg-bunker-800 px-8 text-mineshaft-50 dark:[color-scheme:dark]">
<Helmet>
<title>Infisical Maintenance Mode</title>
<link rel="icon" href="/infisical.ico" />
</Helmet>
<img
src="/images/maintenance.png"
height={175}
width={300}
alt="maintenance mode"
className="w-[40rem]"
/>
<p className="mx-8 mb-4 flex justify-center bg-gradient-to-tr from-mineshaft-300 to-white bg-clip-text text-4xl font-bold text-transparent md:mx-16">
Scheduled Maintenance
</p>
<div className="mt-2 text-center text-lg text-bunker-300">
Infisical is undergoing planned maintenance. <br /> No action is required on your end —
your applications will continue to fetch secrets.
<br /> If you have questions, please{" "}
<a
className="text-bunker-300 underline decoration-primary-800 underline-offset-4 duration-200 hover:text-mineshaft-100 hover:decoration-primary-600"
href="https://infisical.com/slack"
target="_blank"
rel="noopener noreferrer"
>
join our Slack community
</a>
.
</div>
</div>
);
}
if (isLoading) {
return (
<div className="bg-bunker-800">
<ContentLoader text="Loading configurations" />
</div>
);
}
return <ServerConfigContext.Provider value={value}>{children}</ServerConfigContext.Provider>;
};
import { adminQueryKeys, fetchServerConfig } from "@app/hooks/api/admin/queries";
import { useSuspenseQuery } from "@tanstack/react-query";
export const useServerConfig = () => {
const config = useRouteContext({ from: "__root__", select: (el) => el.serverConfig });
const { data: config } = useSuspenseQuery({
queryKey: adminQueryKeys.serverConfig(),
queryFn: fetchServerConfig
});
return { config };
};

View File

@@ -1 +1 @@
export { ServerConfigProvider, useServerConfig } from "./ServerConfigContext";
export { useServerConfig } from "./ServerConfigContext";

View File

@@ -1,41 +1,5 @@
import { createContext, ReactNode, useContext, useMemo } from "react";
import { useRouteContext } from "@tanstack/react-router";
import { useGetOrgSubscription } from "@app/hooks/api";
import { SubscriptionPlan } from "@app/hooks/api/types";
import { useOrganization } from "../OrganizationContext";
type TSubscriptionContext = {
subscription?: SubscriptionPlan;
isLoading: boolean;
};
const SubscriptionContext = createContext<TSubscriptionContext | null>(null);
type Props = {
children: ReactNode;
};
export const SubscriptionProvider = ({ children }: Props): JSX.Element => {
const { currentOrg } = useOrganization();
const { data, isLoading } = useGetOrgSubscription({
orgID: currentOrg?.id || ""
});
// memorize the workspace details for the context
const value = useMemo<TSubscriptionContext>(
() => ({
subscription: data,
isLoading
}),
[data, isLoading]
);
return <SubscriptionContext.Provider value={value}>{children}</SubscriptionContext.Provider>;
};
export const useSubscription = () => {
const subscription = useRouteContext({
from: "/_authenticate/_org_details",

View File

@@ -1 +1 @@
export { SubscriptionProvider, useSubscription } from "./SubscriptionContext";
export { useSubscription } from "./SubscriptionContext";

View File

@@ -1,49 +1,5 @@
import { createContext, ReactNode, useContext, useMemo } from "react";
import { useRouteContext } from "@tanstack/react-router";
import { useGetUser } from "@app/hooks/api";
import { User, UserEnc } from "@app/hooks/api/types";
type TUserContext = {
user: User & UserEnc;
isLoading: boolean;
};
const UserContext = createContext<TUserContext | null>(null);
type Props = {
children: ReactNode;
};
export const UserProvider = ({ children }: Props): JSX.Element => {
const { data, isLoading } = useGetUser();
// memorize the workspace details for the context
const value = useMemo<TUserContext>(() => {
return {
user: data!,
isLoading
};
}, [data, isLoading]);
if (isLoading) {
return (
<div className="flex h-screen w-screen items-center justify-center bg-bunker-800">
<img
src="/images/loading/loading.gif"
height={70}
width={120}
decoding="async"
loading="lazy"
alt="infisical loading indicator"
/>
</div>
);
}
return <UserContext.Provider value={value}>{children}</UserContext.Provider>;
};
export const useUser = () => {
const user = useRouteContext({ from: "/_authenticate", select: (el) => el.user })!;

View File

@@ -1 +1 @@
export { UserProvider, useUser } from "./UserContext";
export { useUser } from "./UserContext";

View File

@@ -1,9 +1,8 @@
export { AuthProvider } from "./AuthContext";
export { OrgProvider, useOrganization } from "./OrganizationContext";
export { useOrganization } from "./OrganizationContext";
export type { TOrgPermission } from "./OrgPermissionContext";
export {
OrgPermissionActions,
OrgPermissionProvider,
OrgPermissionSubjects,
useOrgPermission
} from "./OrgPermissionContext";
@@ -16,7 +15,7 @@ export {
ProjectPermissionSub,
useProjectPermission
} from "./ProjectPermissionContext";
export { ServerConfigProvider, useServerConfig } from "./ServerConfigContext";
export { SubscriptionProvider, useSubscription } from "./SubscriptionContext";
export { UserProvider, useUser } from "./UserContext";
export { useServerConfig } from "./ServerConfigContext";
export { useSubscription } from "./SubscriptionContext";
export { useUser } from "./UserContext";
export { useWorkspace, WorkspaceProvider } from "./WorkspaceContext";

View File

@@ -60,16 +60,15 @@ export const initProjectHelper = async ({ projectName }: { projectName: string }
return project;
};
export const getProjectHomePage = (workspace: Workspace) => {
if (workspace.type === ProjectType.SecretManager) {
return `/${workspace.type}/${workspace.id}/secrets/overview`;
return `/${workspace.type}/$projectId/secrets/overview` as const;
}
if (workspace.type === ProjectType.CertificateManager) {
return `/${workspace.type}/${workspace.id}/certificates`;
return `/${workspace.type}/$projectId/certificates` as const;
}
return `/${workspace.type}/${workspace.id}/kms`;
return `/${workspace.type}/$projectId/kms` as const;
};
export const getProjectTitle = (type: ProjectType) => {

View File

@@ -89,10 +89,9 @@ export const useSelectOrganization = () => {
return data;
},
onSuccess: () => {
queryClient.invalidateQueries([
organizationKeys.getUserOrganizations,
workspaceKeys.getAllUserWorkspace
]);
queryClient.invalidateQueries({
queryKey: [organizationKeys.getUserOrganizations, workspaceKeys.getAllUserWorkspace]
});
}
});
};

View File

@@ -21,7 +21,9 @@ export const useImportEnvKey = () => {
"Content-Type": "multipart/form-data"
},
onUploadProgress: (progressEvent) => {
const percentCompleted = Math.round((progressEvent.loaded * 100) / progressEvent.total);
const percentCompleted = Math.round(
(progressEvent.loaded * 100) / (progressEvent?.total || 1)
);
console.log(`Upload Progress: ${percentCompleted}%`);
}
});

View File

@@ -143,7 +143,7 @@ const fetchSecretApprovalRequestList = async ({
export const useGetSecretApprovalRequests = ({
workspaceId,
environment,
options = object,
options = {},
status,
limit = 20,
committer

View File

@@ -1,5 +1,7 @@
import { apiRequest } from "@app/config/request";
import { useQuery } from "@tanstack/react-query";
import { apiRequest } from "@app/config/request";
import { TSecretScanningGitRisks } from "./types";
export const secretScanningQueryKeys = {

View File

@@ -49,9 +49,10 @@ export const useGetActiveSharedSecretById = ({
hashedHex: string | null;
password?: string;
}) => {
return useQuery<TViewSharedSecretResponse>(
secretSharingKeys.getSecretById({ id: sharedSecretId, hashedHex, password }),
async () => {
return useQuery({
queryKey: secretSharingKeys.getSecretById({ id: sharedSecretId, hashedHex, password }),
queryFn: async () => {
const { data } = await apiRequest.post<TViewSharedSecretResponse>(
`/api/v1/secret-sharing/public/${sharedSecretId}`,
{
@@ -61,9 +62,8 @@ export const useGetActiveSharedSecretById = ({
);
return data;
},
{
enabled: Boolean(sharedSecretId)
}
);
}, {
enabled: Boolean(sharedSecretId)
});
};

View File

@@ -148,14 +148,18 @@ export const usePerformSecretRollback = () => {
return data;
},
onSuccess: (_, { workspaceId, environment, directory }) => {
queryClient.invalidateQueries([
{ workspaceId, environment, secretPath: directory },
"secrets"
]);
queryClient.invalidateQueries([
"secret-folders",
{ projectId: workspaceId, environment, path: directory }
]);
queryClient.invalidateQueries({
queryKey: [
{ workspaceId, environment, secretPath: directory },
"secrets"
]
});
queryClient.invalidateQueries({
queryKey: [
"secret-folders",
{ projectId: workspaceId, environment, path: directory }
]
});
queryClient.invalidateQueries(
secretSnapshotKeys.list({ workspaceId, environment, directory })
);

View File

@@ -148,9 +148,12 @@ export const useGetUserWorkspaces = ({
includeRoles?: boolean;
type?: ProjectType | "all";
} = {}) =>
useQuery(workspaceKeys.getAllUserWorkspace(type || ""), () =>
fetchUserWorkspaces(includeRoles, type)
);
useQuery({
queryKey: workspaceKeys.getAllUserWorkspace(type || ""),
queryFn: () =>
fetchUserWorkspaces(includeRoles, type)
});
const fetchUserWorkspaceMemberships = async (orgId: string) => {
const { data } = await apiRequest.get<Record<string, Workspace[]>>(
@@ -324,7 +327,9 @@ export const useDeleteWorkspace = () => {
},
onSuccess: (dto) => {
queryClient.invalidateQueries(workspaceKeys.getAllUserWorkspace(dto.type));
queryClient.invalidateQueries(["org-admin-projects"]);
queryClient.invalidateQueries({
queryKey: ["org-admin-projects"]
});
}
});
};

View File

@@ -74,7 +74,9 @@ export const OrganizationLayout = () => {
<div className="px-1">
<Menu className="mt-4">
<Link
to={`/organization/$organizationId/${ProjectType.SecretManager}` as const}
to={
`/organization/$organizationId/${ProjectType.SecretManager}/overview` as const
}
params={{ organizationId: currentOrg.id }}
activeOptions={{ exact: true }}
>
@@ -86,7 +88,7 @@ export const OrganizationLayout = () => {
</Link>
<Link
to={
`/organization/$organizationid/${ProjectType.CertificateManager}` as const
`/organization/$organizationId/${ProjectType.CertificateManager}/overview` as const
}
params={{ organizationId: currentOrg.id }}
activeOptions={{ exact: true }}
@@ -98,7 +100,7 @@ export const OrganizationLayout = () => {
)}
</Link>
<Link
to={`/organization/$organizationId/${ProjectType.KMS}` as const}
to={`/organization/$organizationId/${ProjectType.KMS}/overview` as const}
params={{ organizationId: currentOrg.id }}
activeOptions={{ exact: true }}
>

View File

@@ -32,7 +32,9 @@ export const PersonalSettingsLayout = () => {
<nav className="items-between flex h-full flex-col justify-between overflow-y-auto dark:[color-scheme:dark]">
<div className="flex-grow">
<Link
to={`/organization/$organizationId/${ProjectType.SecretManager}` as const}
to={
`/organization/$organizationId/${ProjectType.SecretManager}/overview` as const
}
params={{ organizationId }}
>
<div className="my-6 flex cursor-default items-center justify-center pr-2 text-sm text-mineshaft-300 hover:text-mineshaft-100">

View File

@@ -30,7 +30,7 @@ const RootPage = () => {
export const Route = createRootRouteWithContext<TRouterContext>()({
component: RootPage,
beforeLoad: async ({ context }) => {
const serverConfig = await context.queryClient.fetchQuery({
const serverConfig = await context.queryClient.ensureQueryData({
queryKey: adminQueryKeys.serverConfig(),
queryFn: fetchServerConfig
});

View File

@@ -1,6 +1,7 @@
import { useState } from "react";
import { faEllipsis, faMagnifyingGlass, faSignIn } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { useNavigate } from "@tanstack/react-router";
import { format } from "date-fns";
import { motion } from "framer-motion";
@@ -31,7 +32,6 @@ import {
import { withPermission } from "@app/hoc";
import { useDebounce } from "@app/hooks";
import { useOrgAdminAccessProject, useOrgAdminGetProjects } from "@app/hooks/api";
import { useNavigate } from "@tanstack/react-router";
import { ProjectType } from "@app/hooks/api/workspace/types";
export const OrgAdminProjects = withPermission(

View File

@@ -1,38 +1,31 @@
import { useState } from 'react'
import { Helmet } from 'react-helmet'
import { useTranslation } from 'react-i18next'
import { createFileRoute } from '@tanstack/react-router'
import { useState } from "react";
import { Helmet } from "react-helmet";
import { useTranslation } from "react-i18next";
import { createFileRoute } from "@tanstack/react-router";
import { Tab, TabList, TabPanel, Tabs } from '@app/components/v2'
import { Tab, TabList, TabPanel, Tabs } from "@app/components/v2";
import { OrgAdminProjects } from './-components/OrgAdminProjects'
import { OrgAdminProjects } from "./-components/OrgAdminProjects";
enum TabSections {
Projects = 'projects',
Projects = "projects"
}
const OrgAdminPage = () => {
const { t } = useTranslation()
const [activeTab, setActiveTab] = useState<TabSections>(TabSections.Projects)
const { t } = useTranslation();
const [activeTab, setActiveTab] = useState<TabSections>(TabSections.Projects);
return (
<>
<Helmet>
<title>
{t('common.head-title', { title: t('settings.org.title') })}
</title>
<title>{t("common.head-title", { title: t("settings.org.title") })}</title>
<link rel="icon" href="/infisical.ico" />
</Helmet>
<div className="flex w-full justify-center bg-bunker-800 py-6 text-white">
<div className="w-full max-w-6xl px-6">
<div className="mb-4">
<p className="text-3xl font-semibold text-gray-200">
Organization Admin Console
</p>
<p className="text-3xl font-semibold text-gray-200">Organization Admin Console</p>
</div>
<Tabs
value={activeTab}
onValueChange={(el) => setActiveTab(el as TabSections)}
>
<Tabs value={activeTab} onValueChange={(el) => setActiveTab(el as TabSections)}>
<TabList>
<Tab value={TabSections.Projects}>Projects</Tab>
</TabList>
@@ -43,11 +36,11 @@ const OrgAdminPage = () => {
</div>
</div>
</>
)
}
);
};
export const Route = createFileRoute(
'/_authenticate/_org_details/_org-layout/organization/$organizationId/admin/',
"/_authenticate/_org_details/_org-layout/organization/$organizationId/admin/"
)({
component: OrgAdminPage,
})
component: OrgAdminPage
});

View File

@@ -1,52 +1,50 @@
import { Helmet } from 'react-helmet'
import { useTranslation } from 'react-i18next'
import { createFileRoute } from '@tanstack/react-router'
import { Helmet } from "react-helmet";
import { useTranslation } from "react-i18next";
import { createFileRoute } from "@tanstack/react-router";
import { OrgPermissionActions, OrgPermissionSubjects } from '@app/context'
import { withPermission } from '@app/hoc'
import { OrgPermissionActions, OrgPermissionSubjects } from "@app/context";
import { withPermission } from "@app/hoc";
import { BillingTabGroup } from './-components'
import { BillingTabGroup } from "./-components";
const BillngPage = withPermission(
() => {
const { t } = useTranslation()
const { t } = useTranslation();
return (
<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="my-6">
<p className="text-3xl font-semibold text-gray-200">
{t('billing.title')}
</p>
<p className="text-3xl font-semibold text-gray-200">{t("billing.title")}</p>
<div />
</div>
<BillingTabGroup />
</div>
</div>
)
);
},
{
action: OrgPermissionActions.Read,
subject: OrgPermissionSubjects.Billing,
},
)
subject: OrgPermissionSubjects.Billing
}
);
const BillingRoute = () => {
const { t } = useTranslation()
const { t } = useTranslation();
return (
<div className="h-full bg-bunker-800">
<Helmet>
<title>{t('common.head-title', { title: t('billing.title') })}</title>
<title>{t("common.head-title", { title: t("billing.title") })}</title>
<link rel="icon" href="/infisical.ico" />
<meta property="og:image" content="/images/message.png" />
</Helmet>
<BillngPage />
</div>
)
}
);
};
export const Route = createFileRoute(
'/_authenticate/_org_details/_org-layout/organization/$organizationId/billing/',
"/_authenticate/_org_details/_org-layout/organization/$organizationId/billing/"
)({
component: BillingRoute,
})
component: BillingRoute
});

View File

@@ -1,12 +1,12 @@
import { Helmet } from 'react-helmet'
import { useTranslation } from 'react-i18next'
import { faChevronLeft, faEllipsis } from '@fortawesome/free-solid-svg-icons'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { createFileRoute, useNavigate, useParams } from '@tanstack/react-router'
import { twMerge } from 'tailwind-merge'
import { Helmet } from "react-helmet";
import { useTranslation } from "react-i18next";
import { faChevronLeft, faEllipsis } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { createFileRoute, useNavigate, useParams } from "@tanstack/react-router";
import { twMerge } from "tailwind-merge";
import { createNotification } from '@app/components/notifications'
import { OrgPermissionCan } from '@app/components/permissions'
import { createNotification } from "@app/components/notifications";
import { OrgPermissionCan } from "@app/components/permissions";
import {
Button,
DeleteActionModal,
@@ -16,81 +16,74 @@ import {
DropdownMenuTrigger,
Spinner,
Tooltip,
UpgradePlanModal,
} from '@app/components/v2'
import {
OrgPermissionActions,
OrgPermissionSubjects,
useOrganization,
} from '@app/context'
import { withPermission } from '@app/hoc'
import { useDeleteGroup } from '@app/hooks/api'
import { useGetGroupById } from '@app/hooks/api/groups/queries'
import { usePopUp } from '@app/hooks/usePopUp'
UpgradePlanModal
} from "@app/components/v2";
import { OrgPermissionActions, OrgPermissionSubjects, useOrganization } from "@app/context";
import { withPermission } from "@app/hoc";
import { useDeleteGroup } from "@app/hooks/api";
import { useGetGroupById } from "@app/hooks/api/groups/queries";
import { usePopUp } from "@app/hooks/usePopUp";
import { GroupCreateUpdateModal } from './-components/GroupCreateUpdateModal'
import { GroupMembersSection } from './-components/GroupMembersSection'
import { GroupDetailsSection } from './-components'
import { GroupCreateUpdateModal } from "./-components/GroupCreateUpdateModal";
import { GroupMembersSection } from "./-components/GroupMembersSection";
import { GroupDetailsSection } from "./-components";
export enum TabSections {
Member = 'members',
Groups = 'groups',
Roles = 'roles',
Identities = 'identities',
Member = "members",
Groups = "groups",
Roles = "roles",
Identities = "identities"
}
const GroupPage = withPermission(
() => {
const navigate = useNavigate()
const navigate = useNavigate();
const params = useParams({
from: '/_authenticate/_org_details/_org-layout/organization/$organizationId/groups/$groupId',
})
const groupId = params.groupId as string
const { currentOrg } = useOrganization()
from: "/_authenticate/_org_details/_org-layout/organization/$organizationId/groups/$groupId/"
});
const groupId = params.groupId as string;
const { currentOrg } = useOrganization();
const { data, isLoading } = useGetGroupById(groupId)
const { data, isLoading } = useGetGroupById(groupId);
const { mutateAsync: deleteMutateAsync } = useDeleteGroup()
const { mutateAsync: deleteMutateAsync } = useDeleteGroup();
const { popUp, handlePopUpOpen, handlePopUpClose, handlePopUpToggle } =
usePopUp(['groupCreateUpdate', 'deleteGroup', 'upgradePlan'] as const)
const { popUp, handlePopUpOpen, handlePopUpClose, handlePopUpToggle } = usePopUp([
"groupCreateUpdate",
"deleteGroup",
"upgradePlan"
] as const);
const onDeleteGroupSubmit = async ({
name,
id,
}: {
name: string
id: string
}) => {
const onDeleteGroupSubmit = async ({ name, id }: { name: string; id: string }) => {
try {
await deleteMutateAsync({
id,
})
id
});
createNotification({
text: `Successfully deleted the ${name} group`,
type: 'success',
})
type: "success"
});
navigate({
to: '/organization/$organizationId/members' as const,
to: "/organization/$organizationId/members" as const,
params: {
organizationId: currentOrg.id,
organizationId: currentOrg.id
},
search: {
selectedTab: TabSections.Groups,
},
})
selectedTab: TabSections.Groups
}
});
} catch (err) {
console.error(err)
console.error(err);
createNotification({
text: `Failed to delete the ${name} group`,
type: 'error',
})
type: "error"
});
}
handlePopUpClose('deleteGroup')
}
handlePopUpClose("deleteGroup");
};
if (isLoading) return <Spinner size="sm" className="ml-2 mt-2" />
if (isLoading) return <Spinner size="sm" className="ml-2 mt-2" />;
return (
<div className="container mx-auto flex flex-col justify-between bg-bunker-800 text-white">
@@ -102,23 +95,21 @@ const GroupPage = withPermission(
leftIcon={<FontAwesomeIcon icon={faChevronLeft} />}
onClick={() => {
navigate({
to: '/organization/$organizationId/members' as const,
to: "/organization/$organizationId/members" as const,
params: {
organizationId: currentOrg.id,
organizationId: currentOrg.id
},
search: {
selectedTab: TabSections.Groups,
},
})
selectedTab: TabSections.Groups
}
});
}}
className="mb-4"
>
Groups
</Button>
<div className="mb-4 flex items-center justify-between">
<p className="text-3xl font-semibold text-white">
{data.group.name}
</p>
<p className="text-3xl font-semibold text-white">{data.group.name}</p>
<DropdownMenu>
<DropdownMenuTrigger asChild className="rounded-lg">
<div className="hover:text-primary-400 data-[state=open]:text-primary-400">
@@ -128,23 +119,19 @@ const GroupPage = withPermission(
</div>
</DropdownMenuTrigger>
<DropdownMenuContent align="start" className="p-1">
<OrgPermissionCan
I={OrgPermissionActions.Edit}
a={OrgPermissionSubjects.Groups}
>
<OrgPermissionCan I={OrgPermissionActions.Edit} a={OrgPermissionSubjects.Groups}>
{(isAllowed) => (
<DropdownMenuItem
className={twMerge(
!isAllowed &&
'pointer-events-none cursor-not-allowed opacity-50',
!isAllowed && "pointer-events-none cursor-not-allowed opacity-50"
)}
onClick={async () => {
handlePopUpOpen('groupCreateUpdate', {
handlePopUpOpen("groupCreateUpdate", {
groupId,
name: data.group.name,
slug: data.group.slug,
role: data.group.role,
})
role: data.group.role
});
}}
disabled={!isAllowed}
>
@@ -160,14 +147,14 @@ const GroupPage = withPermission(
<DropdownMenuItem
className={twMerge(
isAllowed
? 'hover:!bg-red-500 hover:!text-white'
: 'pointer-events-none cursor-not-allowed opacity-50',
? "hover:!bg-red-500 hover:!text-white"
: "pointer-events-none cursor-not-allowed opacity-50"
)}
onClick={async () => {
handlePopUpOpen('deleteGroup', {
handlePopUpOpen("deleteGroup", {
id: groupId,
name: data.group.name,
})
name: data.group.name
});
}}
disabled={!isAllowed}
>
@@ -180,15 +167,9 @@ const GroupPage = withPermission(
</div>
<div className="flex">
<div className="mr-4 w-96">
<GroupDetailsSection
groupId={groupId}
handlePopUpOpen={handlePopUpOpen}
/>
<GroupDetailsSection groupId={groupId} handlePopUpOpen={handlePopUpOpen} />
</div>
<GroupMembersSection
groupId={groupId}
groupSlug={data.group.slug}
/>
<GroupMembersSection groupId={groupId} groupSlug={data.group.slug} />
</div>
</div>
)}
@@ -200,46 +181,40 @@ const GroupPage = withPermission(
<DeleteActionModal
isOpen={popUp.deleteGroup.isOpen}
title={`Are you sure want to delete the group named ${
(popUp?.deleteGroup?.data as { name: string })?.name || ''
(popUp?.deleteGroup?.data as { name: string })?.name || ""
}?`}
onChange={(isOpen) => handlePopUpToggle('deleteGroup', isOpen)}
onChange={(isOpen) => handlePopUpToggle("deleteGroup", isOpen)}
deleteKey="confirm"
onDeleteApproved={() =>
onDeleteGroupSubmit(
popUp?.deleteGroup?.data as { name: string; id: string },
)
onDeleteGroupSubmit(popUp?.deleteGroup?.data as { name: string; id: string })
}
/>
<UpgradePlanModal
isOpen={popUp.upgradePlan.isOpen}
onOpenChange={(isOpen) => handlePopUpToggle('upgradePlan', isOpen)}
text={
(popUp.upgradePlan?.data as { description: string })?.description
}
onOpenChange={(isOpen) => handlePopUpToggle("upgradePlan", isOpen)}
text={(popUp.upgradePlan?.data as { description: string })?.description}
/>
</div>
)
);
},
{ action: OrgPermissionActions.Read, subject: OrgPermissionSubjects.Groups },
)
{ action: OrgPermissionActions.Read, subject: OrgPermissionSubjects.Groups }
);
const GroupDetailPage = () => {
const { t } = useTranslation()
const { t } = useTranslation();
return (
<>
<Helmet>
<title>
{t('common.head-title', { title: t('settings.org.title') })}
</title>
<title>{t("common.head-title", { title: t("settings.org.title") })}</title>
<link rel="icon" href="/infisical.ico" />
</Helmet>
<GroupPage />
</>
)
}
);
};
export const Route = createFileRoute(
'/_authenticate/_org_details/_org-layout/organization/$organizationId/groups/$groupId/',
"/_authenticate/_org_details/_org-layout/organization/$organizationId/groups/$groupId/"
)({
component: GroupDetailPage,
})
component: GroupDetailPage
});

View File

@@ -1,14 +1,14 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
import { useState } from 'react'
import { Helmet } from 'react-helmet'
import { useTranslation } from 'react-i18next'
import { faChevronLeft, faEllipsis } from '@fortawesome/free-solid-svg-icons'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { createFileRoute, useNavigate, useParams } from '@tanstack/react-router'
import { twMerge } from 'tailwind-merge'
import { useState } from "react";
import { Helmet } from "react-helmet";
import { useTranslation } from "react-i18next";
import { faChevronLeft, faEllipsis } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { createFileRoute, useNavigate, useParams } from "@tanstack/react-router";
import { twMerge } from "tailwind-merge";
import { createNotification } from '@app/components/notifications'
import { OrgPermissionCan } from '@app/components/permissions'
import { createNotification } from "@app/components/notifications";
import { OrgPermissionCan } from "@app/components/permissions";
import {
Button,
DeleteActionModal,
@@ -17,168 +17,155 @@ import {
DropdownMenuItem,
DropdownMenuTrigger,
Tooltip,
UpgradePlanModal,
} from '@app/components/v2'
import {
OrgPermissionActions,
OrgPermissionSubjects,
useOrganization,
} from '@app/context'
import { withPermission } from '@app/hoc'
UpgradePlanModal
} from "@app/components/v2";
import { OrgPermissionActions, OrgPermissionSubjects, useOrganization } from "@app/context";
import { withPermission } from "@app/hoc";
import {
IdentityAuthMethod,
useDeleteIdentity,
useGetIdentityById,
useRevokeIdentityTokenAuthToken,
useRevokeIdentityUniversalAuthClientSecret,
} from '@app/hooks/api'
import { Identity } from '@app/hooks/api/identities/types'
import { usePopUp } from '@app/hooks/usePopUp'
import { TabSections } from '@app/types/org'
useRevokeIdentityUniversalAuthClientSecret
} from "@app/hooks/api";
import { Identity } from "@app/hooks/api/identities/types";
import { usePopUp } from "@app/hooks/usePopUp";
import { TabSections } from "@app/types/org";
import { IdentityAuthMethodModal } from '../../members/-components/OrgIdentityTab/components/IdentitySection/IdentityAuthMethodModal'
import { IdentityModal } from '../../members/-components/OrgIdentityTab/components/IdentitySection/IdentityModal'
import { IdentityUniversalAuthClientSecretModal } from '../../members/-components/OrgIdentityTab/components/IdentitySection/IdentityUniversalAuthClientSecretModal'
import { IdentityAuthMethodModal } from "../../members/-components/OrgIdentityTab/components/IdentitySection/IdentityAuthMethodModal";
import { IdentityModal } from "../../members/-components/OrgIdentityTab/components/IdentitySection/IdentityModal";
import { IdentityUniversalAuthClientSecretModal } from "../../members/-components/OrgIdentityTab/components/IdentitySection/IdentityUniversalAuthClientSecretModal";
import {
IdentityAuthenticationSection,
IdentityClientSecretModal,
IdentityDetailsSection,
IdentityProjectsSection,
IdentityTokenListModal,
IdentityTokenModal,
} from './-components'
IdentityTokenModal
} from "./-components";
export const IdentitySection = withPermission(
() => {
const navigate = useNavigate()
const navigate = useNavigate();
const params = useParams({
from: '/_authenticate/_org_details/_org-layout/organization/$organizationId/identities/$identityId',
})
const identityId = params.identityId as string
const { currentOrg } = useOrganization()
const orgId = currentOrg?.id || ''
const { data } = useGetIdentityById(identityId)
const { mutateAsync: deleteIdentity } = useDeleteIdentity()
const { mutateAsync: revokeToken } = useRevokeIdentityTokenAuthToken()
const { mutateAsync: revokeClientSecret } =
useRevokeIdentityUniversalAuthClientSecret()
from: "/_authenticate/_org_details/_org-layout/organization/$organizationId/identities/$identityId/"
});
const identityId = params.identityId as string;
const { currentOrg } = useOrganization();
const orgId = currentOrg?.id || "";
const { data } = useGetIdentityById(identityId);
const { mutateAsync: deleteIdentity } = useDeleteIdentity();
const { mutateAsync: revokeToken } = useRevokeIdentityTokenAuthToken();
const { mutateAsync: revokeClientSecret } = useRevokeIdentityUniversalAuthClientSecret();
const [selectedAuthMethod, setSelectedAuthMethod] = useState<
Identity['authMethods'][number] | null
>(null)
Identity["authMethods"][number] | null
>(null);
const { popUp, handlePopUpOpen, handlePopUpClose, handlePopUpToggle } =
usePopUp([
'identity',
'deleteIdentity',
'identityAuthMethod',
'revokeAuthMethod',
'token',
'tokenList',
'revokeToken',
'clientSecret',
'revokeClientSecret',
'universalAuthClientSecret', // list of client secrets
'upgradePlan',
] as const)
const { popUp, handlePopUpOpen, handlePopUpClose, handlePopUpToggle } = usePopUp([
"identity",
"deleteIdentity",
"identityAuthMethod",
"revokeAuthMethod",
"token",
"tokenList",
"revokeToken",
"clientSecret",
"revokeClientSecret",
"universalAuthClientSecret", // list of client secrets
"upgradePlan"
] as const);
const onDeleteIdentitySubmit = async (id: string) => {
try {
await deleteIdentity({
identityId: id,
organizationId: orgId,
})
organizationId: orgId
});
createNotification({
text: 'Successfully deleted identity',
type: 'success',
})
text: "Successfully deleted identity",
type: "success"
});
handlePopUpClose('deleteIdentity')
handlePopUpClose("deleteIdentity");
navigate({
to: `/organization/${orgId}/members` as const,
search: {
selectedTab: TabSections.Identities,
to: "/organization/$organizationId/members",
params: {
organizationId: orgId
},
})
search: {
selectedTab: TabSections.Identities
}
});
} catch (err) {
console.error(err)
const error = err as any
const text =
error?.response?.data?.message ?? 'Failed to delete identity'
console.error(err);
const error = err as any;
const text = error?.response?.data?.message ?? "Failed to delete identity";
createNotification({
text,
type: 'error',
})
type: "error"
});
}
}
};
const onRevokeTokenSubmit = async ({
identityId: parentIdentityId,
tokenId,
name,
name
}: {
identityId: string
tokenId: string
name: string
identityId: string;
tokenId: string;
name: string;
}) => {
try {
await revokeToken({
identityId: parentIdentityId,
tokenId,
})
tokenId
});
handlePopUpClose('revokeToken')
handlePopUpClose("revokeToken");
createNotification({
text: `Successfully revoked token ${name ?? ''}`,
type: 'success',
})
text: `Successfully revoked token ${name ?? ""}`,
type: "success"
});
} catch (err) {
console.error(err)
const error = err as any
const text =
error?.response?.data?.message ?? 'Failed to delete identity'
console.error(err);
const error = err as any;
const text = error?.response?.data?.message ?? "Failed to delete identity";
createNotification({
text,
type: 'error',
})
type: "error"
});
}
}
};
const onDeleteClientSecretSubmit = async ({
clientSecretId,
}: {
clientSecretId: string
}) => {
const onDeleteClientSecretSubmit = async ({ clientSecretId }: { clientSecretId: string }) => {
try {
if (
!data?.identity.id ||
selectedAuthMethod !== IdentityAuthMethod.UNIVERSAL_AUTH
)
return
if (!data?.identity.id || selectedAuthMethod !== IdentityAuthMethod.UNIVERSAL_AUTH) return;
await revokeClientSecret({
identityId: data?.identity.id,
clientSecretId,
})
clientSecretId
});
handlePopUpToggle('revokeClientSecret', false)
handlePopUpToggle("revokeClientSecret", false);
createNotification({
text: 'Successfully deleted client secret',
type: 'success',
})
text: "Successfully deleted client secret",
type: "success"
});
} catch (err) {
console.error(err)
console.error(err);
createNotification({
text: 'Failed to delete client secret',
type: 'error',
})
text: "Failed to delete client secret",
type: "error"
});
}
}
};
return (
<div className="container mx-auto flex flex-col justify-between bg-bunker-800 text-white">
@@ -190,20 +177,21 @@ export const IdentitySection = withPermission(
leftIcon={<FontAwesomeIcon icon={faChevronLeft} />}
onClick={() => {
navigate({
to: `/organization/${orgId}/members` as const,
search: {
selectedTab: TabSections.Identities,
to: "/organization/$organizationId/members",
params: {
organizationId: orgId
},
})
search: {
selectedTab: TabSections.Identities
}
});
}}
className="mb-4"
>
Identities
</Button>
<div className="mb-4 flex items-center justify-between">
<p className="text-3xl font-semibold text-white">
{data.identity.name}
</p>
<p className="text-3xl font-semibold text-white">{data.identity.name}</p>
<DropdownMenu>
<DropdownMenuTrigger asChild className="rounded-lg">
<div className="hover:text-primary-400 data-[state=open]:text-primary-400">
@@ -220,16 +208,15 @@ export const IdentitySection = withPermission(
{(isAllowed) => (
<DropdownMenuItem
className={twMerge(
!isAllowed &&
'pointer-events-none cursor-not-allowed opacity-50',
!isAllowed && "pointer-events-none cursor-not-allowed opacity-50"
)}
onClick={async () => {
handlePopUpOpen('identity', {
handlePopUpOpen("identity", {
identityId,
name: data.identity.name,
role: data.role,
customRole: data.customRole,
})
customRole: data.customRole
});
}}
disabled={!isAllowed}
>
@@ -244,15 +231,14 @@ export const IdentitySection = withPermission(
{(isAllowed) => (
<DropdownMenuItem
className={twMerge(
!isAllowed &&
'pointer-events-none cursor-not-allowed opacity-50',
!isAllowed && "pointer-events-none cursor-not-allowed opacity-50"
)}
onClick={async () => {
handlePopUpOpen('identityAuthMethod', {
handlePopUpOpen("identityAuthMethod", {
identityId,
name: data.identity.name,
allAuthMethods: data.identity.authMethods,
})
allAuthMethods: data.identity.authMethods
});
}}
disabled={!isAllowed}
>
@@ -268,14 +254,14 @@ export const IdentitySection = withPermission(
<DropdownMenuItem
className={twMerge(
isAllowed
? 'hover:!bg-red-500 hover:!text-white'
: 'pointer-events-none cursor-not-allowed opacity-50',
? "hover:!bg-red-500 hover:!text-white"
: "pointer-events-none cursor-not-allowed opacity-50"
)}
onClick={async () => {
handlePopUpOpen('deleteIdentity', {
handlePopUpOpen("deleteIdentity", {
identityId,
name: data.identity.name,
})
name: data.identity.name
});
}}
disabled={!isAllowed}
>
@@ -288,10 +274,7 @@ export const IdentitySection = withPermission(
</div>
<div className="flex">
<div className="mr-4 w-96">
<IdentityDetailsSection
identityId={identityId}
handlePopUpOpen={handlePopUpOpen}
/>
<IdentityDetailsSection identityId={identityId} handlePopUpOpen={handlePopUpOpen} />
<IdentityAuthenticationSection
selectedAuthMethod={selectedAuthMethod}
setSelectedAuthMethod={setSelectedAuthMethod}
@@ -309,19 +292,13 @@ export const IdentitySection = withPermission(
handlePopUpOpen={handlePopUpOpen}
handlePopUpToggle={handlePopUpToggle}
/>
<IdentityTokenModal
popUp={popUp}
handlePopUpToggle={handlePopUpToggle}
/>
<IdentityTokenModal popUp={popUp} handlePopUpToggle={handlePopUpToggle} />
<IdentityTokenListModal
popUp={popUp}
handlePopUpOpen={handlePopUpOpen}
handlePopUpToggle={handlePopUpToggle}
/>
<IdentityClientSecretModal
popUp={popUp}
handlePopUpToggle={handlePopUpToggle}
/>
<IdentityClientSecretModal popUp={popUp} handlePopUpToggle={handlePopUpToggle} />
<IdentityUniversalAuthClientSecretModal
popUp={popUp}
handlePopUpOpen={handlePopUpOpen}
@@ -329,87 +306,82 @@ export const IdentitySection = withPermission(
/>
<UpgradePlanModal
isOpen={popUp.upgradePlan.isOpen}
onOpenChange={(isOpen) => handlePopUpToggle('upgradePlan', isOpen)}
text={
(popUp.upgradePlan?.data as { description: string })?.description
}
onOpenChange={(isOpen) => handlePopUpToggle("upgradePlan", isOpen)}
text={(popUp.upgradePlan?.data as { description: string })?.description}
/>
<DeleteActionModal
isOpen={popUp.deleteIdentity.isOpen}
title={`Are you sure want to delete ${
(popUp?.deleteIdentity?.data as { name: string })?.name || ''
(popUp?.deleteIdentity?.data as { name: string })?.name || ""
}?`}
onChange={(isOpen) => handlePopUpToggle('deleteIdentity', isOpen)}
onChange={(isOpen) => handlePopUpToggle("deleteIdentity", isOpen)}
deleteKey="confirm"
onDeleteApproved={() =>
onDeleteIdentitySubmit(
(popUp?.deleteIdentity?.data as { identityId: string })
?.identityId,
(popUp?.deleteIdentity?.data as { identityId: string })?.identityId
)
}
/>
<DeleteActionModal
isOpen={popUp.revokeToken.isOpen}
title={`Are you sure want to revoke ${
(popUp?.revokeToken?.data as { name: string })?.name || ''
(popUp?.revokeToken?.data as { name: string })?.name || ""
}?`}
onChange={(isOpen) => handlePopUpToggle('revokeToken', isOpen)}
onChange={(isOpen) => handlePopUpToggle("revokeToken", isOpen)}
deleteKey="confirm"
onDeleteApproved={() => {
const revokeTokenData = popUp?.revokeToken?.data as {
identityId: string
tokenId: string
name: string
}
identityId: string;
tokenId: string;
name: string;
};
return onRevokeTokenSubmit(revokeTokenData)
return onRevokeTokenSubmit(revokeTokenData);
}}
/>
<DeleteActionModal
isOpen={popUp.revokeClientSecret.isOpen}
title={`Are you sure want to delete the client secret ${
(popUp?.revokeClientSecret?.data as { clientSecretPrefix: string })
?.clientSecretPrefix || ''
?.clientSecretPrefix || ""
}************?`}
onChange={(isOpen) => handlePopUpToggle('revokeClientSecret', isOpen)}
onChange={(isOpen) => handlePopUpToggle("revokeClientSecret", isOpen)}
deleteKey="confirm"
onDeleteApproved={() => {
const deleteClientSecretData = popUp?.revokeClientSecret?.data as {
clientSecretId: string
clientSecretPrefix: string
}
clientSecretId: string;
clientSecretPrefix: string;
};
return onDeleteClientSecretSubmit({
clientSecretId: deleteClientSecretData.clientSecretId,
})
clientSecretId: deleteClientSecretData.clientSecretId
});
}}
/>
</div>
)
);
},
{
action: OrgPermissionActions.Read,
subject: OrgPermissionSubjects.Identity,
},
)
subject: OrgPermissionSubjects.Identity
}
);
const IdentityDetailPage = () => {
const { t } = useTranslation()
const { t } = useTranslation();
return (
<>
<Helmet>
<title>
{t('common.head-title', { title: t('settings.org.title') })}
</title>
<title>{t("common.head-title", { title: t("settings.org.title") })}</title>
<link rel="icon" href="/infisical.ico" />
</Helmet>
<IdentitySection />
</>
)
}
);
};
export const Route = createFileRoute(
'/_authenticate/_org_details/_org-layout/organization/$organizationId/identities/$identityId/',
"/_authenticate/_org_details/_org-layout/organization/$organizationId/identities/$identityId/"
)({
component: IdentityDetailPage,
})
component: IdentityDetailPage
});

View File

@@ -1,13 +1,13 @@
import { createFileRoute } from '@tanstack/react-router'
import { createFileRoute } from "@tanstack/react-router";
import { ProjectType } from '@app/hooks/api/workspace/types'
import { ProjectType } from "@app/hooks/api/workspace/types";
import { ProductOverview } from '../secret-manager/overview'
import { ProductOverview } from "../secret-manager/overview";
const KeyManagerOverviewPage = () => <ProductOverview type={ProjectType.KMS} />
const KeyManagerOverviewPage = () => <ProductOverview type={ProjectType.KMS} />;
export const Route = createFileRoute(
'/_authenticate/_org_details/_org-layout/organization/$organizationId/kms/overview',
"/_authenticate/_org_details/_org-layout/organization/$organizationId/kms/overview"
)({
component: KeyManagerOverviewPage,
})
component: KeyManagerOverviewPage
});

View File

@@ -15,4 +15,3 @@ export const OrgGroupsTab = () => {
</motion.div>
);
};

View File

@@ -8,6 +8,7 @@ import {
faUsers
} from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { useNavigate } from "@tanstack/react-router";
import { twMerge } from "tailwind-merge";
import { createNotification } from "@app/components/notifications";
@@ -37,7 +38,6 @@ import { usePagination, useResetPageHelper } from "@app/hooks";
import { useGetOrganizationGroups, useGetOrgRoles, useUpdateGroup } from "@app/hooks/api";
import { OrderByDirection } from "@app/hooks/api/generic/types";
import { UsePopUpState } from "@app/hooks/usePopUp";
import { useNavigate } from "@tanstack/react-router";
type Props = {
handlePopUpOpen: (

View File

@@ -1 +1 @@
export { OrgGroupsSection } from "./OrgGroupsSection";
export { OrgGroupsSection } from "./OrgGroupsSection";

View File

@@ -1 +1 @@
export { OrgGroupsSection } from "./OrgGroupsSection";
export { OrgGroupsSection } from "./OrgGroupsSection";

View File

@@ -1 +1 @@
export { OrgGroupsTab } from "./OrgGroupsTab";
export { OrgGroupsTab } from "./OrgGroupsTab";

View File

@@ -6,6 +6,7 @@ import {
faServer
} from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { useNavigate } from "@tanstack/react-router";
import { twMerge } from "tailwind-merge";
import { createNotification } from "@app/components/notifications";
@@ -37,7 +38,6 @@ import { useGetIdentityMembershipOrgs, useGetOrgRoles, useUpdateIdentity } from
import { OrderByDirection } from "@app/hooks/api/generic/types";
import { OrgIdentityOrderBy } from "@app/hooks/api/organization/types";
import { UsePopUpState } from "@app/hooks/usePopUp";
import { useNavigate } from "@tanstack/react-router";
type Props = {
handlePopUpOpen: (
@@ -254,7 +254,13 @@ export const IdentityTable = ({ handlePopUpOpen }: Props) => {
)}
onClick={(e) => {
e.stopPropagation();
navigate.push(`/org/${organizationId}/identities/${id}`);
navigate({
to: "/organization/$organizationId/identities/$identityId",
params: {
organizationId,
identityId: id
}
});
}}
disabled={!isAllowed}
>

View File

@@ -1,54 +1,40 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
import { useEffect, useState } from 'react'
import { Helmet } from 'react-helmet'
import { useTranslation } from 'react-i18next'
import { createFileRoute, useNavigate, useSearch } from '@tanstack/react-router'
import { useEffect, useState } from "react";
import { Helmet } from "react-helmet";
import { useTranslation } from "react-i18next";
import { createFileRoute, useNavigate, useSearch } from "@tanstack/react-router";
import { zodValidator } from "@tanstack/zod-adapter";
import { z } from "zod";
import { Tab, TabList, TabPanel, Tabs } from '@app/components/v2'
import { OrgPermissionActions, OrgPermissionSubjects } from '@app/context'
import { withPermission } from '@app/hoc'
import { TabSections } from '@app/types/org'
import { Tab, TabList, TabPanel, Tabs } from "@app/components/v2";
import { OrgPermissionActions, OrgPermissionSubjects } from "@app/context";
import { withPermission } from "@app/hoc";
import { TabSections } from "@app/types/org";
import {
OrgGroupsTab,
OrgIdentityTab,
OrgMembersTab,
OrgRoleTabSection,
} from './-components'
import { OrgGroupsTab, OrgIdentityTab, OrgMembersTab, OrgRoleTabSection } from "./-components";
export const MembersSection = withPermission(
() => {
const navigate = useNavigate({
from: '/_authenticate/_org_details/_org-layout/organization/$organizationId/members',
})
const search = useSearch({
from: '/_authenticate/_org_details/_org-layout/organization/$organizationId/members',
})
const selectedTab = search.selectedTab as TabSections
const [activeTab, setActiveTab] = useState<TabSections>(TabSections.Member)
useEffect(() => {
if (
selectedTab &&
Object.values(TabSections).includes(selectedTab as TabSections)
) {
setActiveTab(selectedTab)
}
}, [selectedTab])
from: "/organization/$organizationId/members"
});
const selectedTab = useSearch({
from: "/_authenticate/_org_details/_org-layout/organization/$organizationId/members/",
select: (el) => el.selectedTab,
structuralSharing: true
});
const updateSelectedTab = (tab: string) => {
navigate({
search: { ...search, selectedTab: tab },
})
}
search: { selectedTab: tab }
});
};
return (
<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 px-6 py-6">
<p className="mb-4 mr-4 text-3xl font-semibold text-white">
Organization Access Control
</p>
<Tabs value={activeTab} onValueChange={updateSelectedTab}>
<p className="mb-4 mr-4 text-3xl font-semibold text-white">Organization Access Control</p>
<Tabs value={selectedTab} onValueChange={updateSelectedTab}>
<TabList>
<Tab value={TabSections.Member}>Users</Tab>
<Tab value={TabSections.Groups}>Groups</Tab>
@@ -74,29 +60,32 @@ export const MembersSection = withPermission(
</Tabs>
</div>
</div>
)
);
},
{ action: OrgPermissionActions.Read, subject: OrgPermissionSubjects.Member },
)
{ action: OrgPermissionActions.Read, subject: OrgPermissionSubjects.Member }
);
const MembersPage = () => {
const { t } = useTranslation()
const { t } = useTranslation();
return (
<>
<Helmet>
<title>
{t('common.head-title', { title: t('settings.org.title') })}
</title>
<title>{t("common.head-title", { title: t("settings.org.title") })}</title>
<link rel="icon" href="/infisical.ico" />
</Helmet>
<MembersSection />
</>
)
}
);
};
const MembersPageQuerySchema = z.object({
selectedTab: z.string().catch(TabSections.Member)
});
export const Route = createFileRoute(
'/_authenticate/_org_details/_org-layout/organization/$organizationId/members/',
"/_authenticate/_org_details/_org-layout/organization/$organizationId/members/"
)({
component: MembersPage,
})
validateSearch: zodValidator(MembersPageQuerySchema)
});

View File

@@ -7,6 +7,8 @@ import { OrgPermissionActions, OrgPermissionSubjects, useSubscription } from "@a
import { withPermission } from "@app/hoc";
import { OrgUser } from "@app/hooks/api/types";
import { LogsSection } from "../../../../audit-logs/-components";
type Props = {
orgMembership: OrgUser;
};

View File

@@ -1,6 +1,7 @@
import { useMemo } from "react";
import { faTrash } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { useNavigate } from "@tanstack/react-router";
import { createNotification } from "@app/components/notifications";
import { IconButton, Tag, Td, Tooltip, Tr } from "@app/components/v2";
@@ -8,7 +9,6 @@ import { useWorkspace } from "@app/context";
import { ProjectMembershipRole } from "@app/hooks/api/roles/types";
import { TWorkspaceUser } from "@app/hooks/api/types";
import { UsePopUpState } from "@app/hooks/usePopUp";
import { useNavigate } from "@tanstack/react-router";
import { TabSections } from "@app/types/org";
type Props = {

View File

@@ -1,15 +1,16 @@
import { ProjectType } from '@app/hooks/api/workspace/types'
import { createFileRoute, redirect } from '@tanstack/react-router'
import { createFileRoute, redirect } from "@tanstack/react-router";
import { ProjectType } from "@app/hooks/api/workspace/types";
export const Route = createFileRoute(
'/_authenticate/_org_details/_org-layout/organization/$organizationId/overview/',
"/_authenticate/_org_details/_org-layout/organization/$organizationId/overview/"
)({
beforeLoad: ({ context }) => {
throw redirect({
to: `/organization/$organizationId/${ProjectType.SecretManager}` as const,
to: `/organization/$organizationId/${ProjectType.SecretManager}/overview` as const,
params: {
organizationId: context.organizationId,
},
})
},
})
organizationId: context.organizationId
}
});
}
});

View File

@@ -1,6 +1,7 @@
import { useEffect } from "react";
import { Controller, useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { useNavigate } from "@tanstack/react-router";
import { z } from "zod";
import { createNotification } from "@app/components/notifications";
@@ -9,7 +10,6 @@ import { useOrganization } from "@app/context";
import { useCreateOrgRole, useGetOrgRole, useUpdateOrgRole } from "@app/hooks/api";
import { UsePopUpState } from "@app/hooks/usePopUp";
import { slugSchema } from "@app/lib/schemas";
import { useNavigate } from "@tanstack/react-router";
const schema = z
.object({
@@ -104,7 +104,7 @@ export const RoleModal = ({ popUp, handlePopUpToggle }: Props) => {
handlePopUpToggle("role", false);
navigate({
to: "/organization/$organizationId/roles/roleId",
to: "/organization/$organizationId/roles/$roleId",
params: {
organizationId: orgId,
roleId: newRole.id

View File

@@ -1,12 +1,12 @@
import { Helmet } from 'react-helmet'
import { useTranslation } from 'react-i18next'
import { faChevronLeft, faEllipsis } from '@fortawesome/free-solid-svg-icons'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { createFileRoute, useNavigate, useParams } from '@tanstack/react-router'
import { twMerge } from 'tailwind-merge'
import { Helmet } from "react-helmet";
import { useTranslation } from "react-i18next";
import { faChevronLeft, faEllipsis } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { createFileRoute, useNavigate, useParams } from "@tanstack/react-router";
import { twMerge } from "tailwind-merge";
import { createNotification } from '@app/components/notifications'
import { OrgPermissionCan } from '@app/components/permissions'
import { createNotification } from "@app/components/notifications";
import { OrgPermissionCan } from "@app/components/permissions";
import {
Button,
DeleteActionModal,
@@ -14,79 +14,70 @@ import {
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
Tooltip,
} from '@app/components/v2'
import {
OrgPermissionActions,
OrgPermissionSubjects,
useOrganization,
} from '@app/context'
import { withPermission } from '@app/hoc'
import { useDeleteOrgRole, useGetOrgRole } from '@app/hooks/api'
import { usePopUp } from '@app/hooks/usePopUp'
import { TabSections } from '@app/types/org'
Tooltip
} from "@app/components/v2";
import { OrgPermissionActions, OrgPermissionSubjects, useOrganization } from "@app/context";
import { withPermission } from "@app/hoc";
import { useDeleteOrgRole, useGetOrgRole } from "@app/hooks/api";
import { usePopUp } from "@app/hooks/usePopUp";
import { TabSections } from "@app/types/org";
import {
RoleDetailsSection,
RoleModal,
RolePermissionsSection,
} from './-components'
import { RoleDetailsSection, RoleModal, RolePermissionsSection } from "./-components";
export const RolePage = withPermission(
() => {
const navigate = useNavigate()
const navigate = useNavigate();
const params = useParams({
from: '/_authenticate/_org_details/_org-layout/organization/$organizationId/roles/$roleId',
})
const roleId = params.roleId as string
const { currentOrg } = useOrganization()
const orgId = currentOrg?.id || ''
const { data } = useGetOrgRole(orgId, roleId)
const { mutateAsync: deleteOrgRole } = useDeleteOrgRole()
from: "/_authenticate/_org_details/_org-layout/organization/$organizationId/roles/$roleId/"
});
const roleId = params.roleId as string;
const { currentOrg } = useOrganization();
const orgId = currentOrg?.id || "";
const { data } = useGetOrgRole(orgId, roleId);
const { mutateAsync: deleteOrgRole } = useDeleteOrgRole();
const { popUp, handlePopUpOpen, handlePopUpClose, handlePopUpToggle } =
usePopUp(['role', 'deleteOrgRole'] as const)
const { popUp, handlePopUpOpen, handlePopUpClose, handlePopUpToggle } = usePopUp([
"role",
"deleteOrgRole"
] as const);
const onDeleteOrgRoleSubmit = async () => {
try {
if (!orgId || !roleId) return
if (!orgId || !roleId) return;
await deleteOrgRole({
orgId,
id: roleId,
})
id: roleId
});
createNotification({
text: 'Successfully deleted organization role',
type: 'success',
})
text: "Successfully deleted organization role",
type: "success"
});
handlePopUpClose('deleteOrgRole')
handlePopUpClose("deleteOrgRole");
navigate({
to: '/organization/$organizationId/members' as const,
to: "/organization/$organizationId/members" as const,
params: {
organizationId: currentOrg.id,
organizationId: currentOrg.id
},
search: {
selectedTab: TabSections.Roles,
},
})
selectedTab: TabSections.Roles
}
});
} catch (err) {
console.error(err)
const error = err as any
const text =
error?.response?.data?.message ?? 'Failed to delete organization role'
console.error(err);
const error = err as any;
const text = error?.response?.data?.message ?? "Failed to delete organization role";
createNotification({
text,
type: 'error',
})
type: "error"
});
}
}
};
const isCustomRole = !['admin', 'member', 'no-access'].includes(
data?.slug ?? '',
)
const isCustomRole = !["admin", "member", "no-access"].includes(data?.slug ?? "");
return (
<div className="container mx-auto flex flex-col justify-between bg-bunker-800 text-white">
@@ -98,14 +89,14 @@ export const RolePage = withPermission(
leftIcon={<FontAwesomeIcon icon={faChevronLeft} />}
onClick={() => {
navigate({
to: '/organization/$organizationId/members' as const,
to: "/organization/$organizationId/members" as const,
params: {
organizationId: currentOrg.id,
organizationId: currentOrg.id
},
search: {
selectedTab: TabSections.Roles,
},
})
selectedTab: TabSections.Roles
}
});
}}
className="mb-4"
>
@@ -123,20 +114,16 @@ export const RolePage = withPermission(
</div>
</DropdownMenuTrigger>
<DropdownMenuContent align="start" className="p-1">
<OrgPermissionCan
I={OrgPermissionActions.Edit}
a={OrgPermissionSubjects.Role}
>
<OrgPermissionCan I={OrgPermissionActions.Edit} a={OrgPermissionSubjects.Role}>
{(isAllowed) => (
<DropdownMenuItem
className={twMerge(
!isAllowed &&
'pointer-events-none cursor-not-allowed opacity-50',
!isAllowed && "pointer-events-none cursor-not-allowed opacity-50"
)}
onClick={async () => {
handlePopUpOpen('role', {
roleId,
})
handlePopUpOpen("role", {
roleId
});
}}
disabled={!isAllowed}
>
@@ -152,11 +139,11 @@ export const RolePage = withPermission(
<DropdownMenuItem
className={twMerge(
isAllowed
? 'hover:!bg-red-500 hover:!text-white'
: 'pointer-events-none cursor-not-allowed opacity-50',
? "hover:!bg-red-500 hover:!text-white"
: "pointer-events-none cursor-not-allowed opacity-50"
)}
onClick={async () => {
handlePopUpOpen('deleteOrgRole')
handlePopUpOpen("deleteOrgRole");
}}
disabled={!isAllowed}
>
@@ -170,10 +157,7 @@ export const RolePage = withPermission(
</div>
<div className="flex">
<div className="mr-4 w-96">
<RoleDetailsSection
roleId={roleId}
handlePopUpOpen={handlePopUpOpen}
/>
<RoleDetailsSection roleId={roleId} handlePopUpOpen={handlePopUpOpen} />
</div>
<RolePermissionsSection roleId={roleId} />
</div>
@@ -182,34 +166,32 @@ export const RolePage = withPermission(
<RoleModal popUp={popUp} handlePopUpToggle={handlePopUpToggle} />
<DeleteActionModal
isOpen={popUp.deleteOrgRole.isOpen}
title={`Are you sure want to delete the organization role ${data?.name ?? ''}?`}
onChange={(isOpen) => handlePopUpToggle('deleteOrgRole', isOpen)}
title={`Are you sure want to delete the organization role ${data?.name ?? ""}?`}
onChange={(isOpen) => handlePopUpToggle("deleteOrgRole", isOpen)}
deleteKey="confirm"
onDeleteApproved={() => onDeleteOrgRoleSubmit()}
/>
</div>
)
);
},
{ action: OrgPermissionActions.Read, subject: OrgPermissionSubjects.Role },
)
{ action: OrgPermissionActions.Read, subject: OrgPermissionSubjects.Role }
);
const OrgRoleRoute = () => {
const { t } = useTranslation()
const { t } = useTranslation();
return (
<>
<Helmet>
<title>
{t('common.head-title', { title: t('settings.org.title') })}
</title>
<title>{t("common.head-title", { title: t("settings.org.title") })}</title>
<link rel="icon" href="/infisical.ico" />
</Helmet>
<RolePage />
</>
)
}
);
};
export const Route = createFileRoute(
'/_authenticate/_org_details/_org-layout/organization/$organizationId/roles/$roleId/',
"/_authenticate/_org_details/_org-layout/organization/$organizationId/roles/$roleId/"
)({
component: OrgRoleRoute,
})
component: OrgRoleRoute
});

View File

@@ -1,9 +1,9 @@
// REFACTOR(akhilmhdh): This file needs to be split into multiple components too complex
import { ReactNode, useMemo, useState } from 'react'
import { Helmet } from 'react-helmet'
import { useTranslation } from 'react-i18next'
import { faFolderOpen, faStar } from '@fortawesome/free-regular-svg-icons'
import { ReactNode, useMemo, useState } from "react";
import { Helmet } from "react-helmet";
import { useTranslation } from "react-i18next";
import { faFolderOpen, faStar } from "@fortawesome/free-regular-svg-icons";
import {
faArrowDownAZ,
faArrowRight,
@@ -14,14 +14,14 @@ import {
faMagnifyingGlass,
faPlus,
faSearch,
faStar as faSolidStar,
} from '@fortawesome/free-solid-svg-icons'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { createFileRoute, useNavigate } from '@tanstack/react-router'
faStar as faSolidStar
} from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { createFileRoute, useNavigate } from "@tanstack/react-router";
import { createNotification } from '@app/components/notifications'
import { OrgPermissionCan } from '@app/components/permissions'
import { NewProjectModal } from '@app/components/projects'
import { createNotification } from "@app/components/notifications";
import { OrgPermissionCan } from "@app/components/permissions";
import { NewProjectModal } from "@app/components/projects";
import {
Button,
IconButton,
@@ -29,89 +29,86 @@ import {
Pagination,
Skeleton,
Tooltip,
UpgradePlanModal,
} from '@app/components/v2'
UpgradePlanModal
} from "@app/components/v2";
import {
OrgPermissionActions,
OrgPermissionSubjects,
useOrganization,
useSubscription,
} from '@app/context'
import { getProjectHomePage } from '@app/helpers/project'
import { usePagination, useResetPageHelper } from '@app/hooks'
import { useGetUserWorkspaces } from '@app/hooks/api'
import { OrderByDirection } from '@app/hooks/api/generic/types'
useSubscription
} from "@app/context";
import { getProjectHomePage } from "@app/helpers/project";
import { usePagination, useResetPageHelper } from "@app/hooks";
import { useGetUserWorkspaces } from "@app/hooks/api";
import { OrderByDirection } from "@app/hooks/api/generic/types";
// import { fetchUserWsKey } from "@app/hooks/api/keys/queries";
import { useFetchServerStatus } from '@app/hooks/api/serverDetails'
import { Workspace } from '@app/hooks/api/types'
import { useUpdateUserProjectFavorites } from '@app/hooks/api/users/mutation'
import { useGetUserProjectFavorites } from '@app/hooks/api/users/queries'
import { ProjectType } from '@app/hooks/api/workspace/types'
import { usePopUp } from '@app/hooks/usePopUp'
import { useFetchServerStatus } from "@app/hooks/api/serverDetails";
import { Workspace } from "@app/hooks/api/types";
import { useUpdateUserProjectFavorites } from "@app/hooks/api/users/mutation";
import { useGetUserProjectFavorites } from "@app/hooks/api/users/queries";
import { ProjectType } from "@app/hooks/api/workspace/types";
import { usePopUp } from "@app/hooks/usePopUp";
enum ProjectsViewMode {
GRID = 'grid',
LIST = 'list',
GRID = "grid",
LIST = "list"
}
enum ProjectOrderBy {
Name = 'name',
Name = "name"
}
const formatTitle = (type: ProjectType) => {
if (type === ProjectType.SecretManager) return 'Secret Management'
if (type === ProjectType.CertificateManager) return 'Cert Management'
return 'Key Management'
}
if (type === ProjectType.SecretManager) return "Secret Management";
if (type === ProjectType.CertificateManager) return "Cert Management";
return "Key Management";
};
const formatDescription = (type: ProjectType) => {
if (type === ProjectType.SecretManager)
return 'Securely store, manage, and rotate various application secrets, such as database credentials, API keys, etc.'
return "Securely store, manage, and rotate various application secrets, such as database credentials, API keys, etc.";
if (type === ProjectType.CertificateManager)
return 'Manage your PKI infrastructure and issue digital certificates for services, applications, and devices.'
return 'Centralize the management of keys for cryptographic operations, such as encryption and decryption.'
}
return "Manage your PKI infrastructure and issue digital certificates for services, applications, and devices.";
return "Centralize the management of keys for cryptographic operations, such as encryption and decryption.";
};
type Props = {
type: ProjectType
}
type: ProjectType;
};
// #TODO: Update all the workspaceIds
export const ProductOverview = ({ type }: Props) => {
const { t } = useTranslation()
const { t } = useTranslation();
const navigate = useNavigate()
const navigate = useNavigate();
const { data: workspaces, isLoading: isWorkspaceLoading } =
useGetUserWorkspaces({ type })
const { currentOrg } = useOrganization()
const orgWorkspaces = workspaces || []
const { data: workspaces, isLoading: isWorkspaceLoading } = useGetUserWorkspaces({ type });
const { currentOrg } = useOrganization();
const orgWorkspaces = workspaces || [];
const { data: projectFavorites, isLoading: isProjectFavoritesLoading } =
useGetUserProjectFavorites(currentOrg?.id)
const { mutateAsync: updateUserProjectFavorites } =
useUpdateUserProjectFavorites()
useGetUserProjectFavorites(currentOrg?.id);
const { mutateAsync: updateUserProjectFavorites } = useUpdateUserProjectFavorites();
const isProjectViewLoading = isWorkspaceLoading || isProjectFavoritesLoading
const isProjectViewLoading = isWorkspaceLoading || isProjectFavoritesLoading;
const { popUp, handlePopUpOpen, handlePopUpToggle } = usePopUp([
'addNewWs',
'upgradePlan',
] as const)
"addNewWs",
"upgradePlan"
] as const);
const [searchFilter, setSearchFilter] = useState('')
const { data: serverDetails } = useFetchServerStatus()
const [searchFilter, setSearchFilter] = useState("");
const { data: serverDetails } = useFetchServerStatus();
const [projectsViewMode, setProjectsViewMode] = useState<ProjectsViewMode>(
(localStorage.getItem('projectsViewMode') as ProjectsViewMode) ||
ProjectsViewMode.GRID,
)
(localStorage.getItem("projectsViewMode") as ProjectsViewMode) || ProjectsViewMode.GRID
);
const { subscription } = useSubscription()
const { subscription } = useSubscription();
const isAddingProjectsAllowed = subscription?.workspaceLimit
? subscription.workspacesUsed < subscription.workspaceLimit
: true
: true;
const isWorkspaceEmpty = !isProjectViewLoading && orgWorkspaces?.length === 0
const isWorkspaceEmpty = !isProjectViewLoading && orgWorkspaces?.length === 0;
const {
setPage,
@@ -121,76 +118,72 @@ export const ProductOverview = ({ type }: Props) => {
offset,
limit,
toggleOrderDirection,
orderDirection,
} = usePagination(ProjectOrderBy.Name, { initPerPage: 24 })
orderDirection
} = usePagination(ProjectOrderBy.Name, { initPerPage: 24 });
const filteredWorkspaces = useMemo(
() =>
orgWorkspaces
.filter((ws) =>
ws?.name?.toLowerCase().includes(searchFilter.toLowerCase()),
)
.filter((ws) => ws?.name?.toLowerCase().includes(searchFilter.toLowerCase()))
.sort((a, b) =>
orderDirection === OrderByDirection.ASC
? a.name.toLowerCase().localeCompare(b.name.toLowerCase())
: b.name.toLowerCase().localeCompare(a.name.toLowerCase()),
: b.name.toLowerCase().localeCompare(a.name.toLowerCase())
),
[searchFilter, orderDirection, orgWorkspaces],
)
[searchFilter, orderDirection, orgWorkspaces]
);
useResetPageHelper({
setPage,
offset,
totalCount: filteredWorkspaces.length,
})
totalCount: filteredWorkspaces.length
});
const { workspacesWithFaveProp } = useMemo(() => {
const workspacesWithFav = filteredWorkspaces
.map((w): Workspace & { isFavorite: boolean } => ({
...w,
isFavorite: Boolean(projectFavorites?.includes(w.id)),
isFavorite: Boolean(projectFavorites?.includes(w.id))
}))
.sort((a, b) => Number(b.isFavorite) - Number(a.isFavorite))
.slice(offset, limit * page)
.slice(offset, limit * page);
return {
workspacesWithFaveProp: workspacesWithFav,
}
}, [filteredWorkspaces, projectFavorites])
workspacesWithFaveProp: workspacesWithFav
};
}, [filteredWorkspaces, projectFavorites]);
const addProjectToFavorites = async (projectId: string) => {
try {
if (currentOrg?.id) {
await updateUserProjectFavorites({
orgId: currentOrg?.id,
projectFavorites: [...(projectFavorites || []), projectId],
})
projectFavorites: [...(projectFavorites || []), projectId]
});
}
} catch {
createNotification({
text: 'Failed to add project to favorites.',
type: 'error',
})
text: "Failed to add project to favorites.",
type: "error"
});
}
}
};
const removeProjectFromFavorites = async (projectId: string) => {
try {
if (currentOrg?.id) {
await updateUserProjectFavorites({
orgId: currentOrg?.id,
projectFavorites: [
...(projectFavorites || []).filter((entry) => entry !== projectId),
],
})
projectFavorites: [...(projectFavorites || []).filter((entry) => entry !== projectId)]
});
}
} catch {
createNotification({
text: 'Failed to remove project from favorites.',
type: 'error',
})
text: "Failed to remove project from favorites.",
type: "error"
});
}
}
};
const renderProjectGridItem = (workspace: Workspace, isFavorite: boolean) => (
// eslint-disable-next-line jsx-a11y/no-static-element-interactions, jsx-a11y/click-events-have-key-events
@@ -198,24 +191,22 @@ export const ProductOverview = ({ type }: Props) => {
onClick={() => {
// TODO(rbr): fix this
navigate({
to: getProjectHomePage(workspace),
})
localStorage.setItem('projectData.id', workspace.id)
to: getProjectHomePage(workspace)
});
localStorage.setItem("projectData.id", workspace.id);
}}
key={workspace.id}
className="flex h-40 min-w-72 cursor-pointer flex-col rounded-md border border-mineshaft-600 bg-mineshaft-800 p-4"
>
<div className="flex flex-row justify-between">
<div className="mt-0 truncate text-lg text-mineshaft-100">
{workspace.name}
</div>
<div className="mt-0 truncate text-lg text-mineshaft-100">{workspace.name}</div>
{isFavorite ? (
<FontAwesomeIcon
icon={faSolidStar}
className="text-sm text-yellow-600 hover:text-mineshaft-400"
onClick={(e) => {
e.stopPropagation()
removeProjectFromFavorites(workspace.id)
e.stopPropagation();
removeProjectFromFavorites(workspace.id);
}}
/>
) : (
@@ -223,8 +214,8 @@ export const ProductOverview = ({ type }: Props) => {
icon={faStar}
className="text-sm text-mineshaft-400 hover:text-mineshaft-300"
onClick={(e) => {
e.stopPropagation()
addProjectToFavorites(workspace.id)
e.stopPropagation();
addProjectToFavorites(workspace.id);
}}
/>
)}
@@ -233,10 +224,10 @@ export const ProductOverview = ({ type }: Props) => {
<div
className="mb-2.5 mt-1 grow text-sm text-mineshaft-300"
style={{
overflow: 'hidden',
display: '-webkit-box',
WebkitBoxOrient: 'vertical',
WebkitLineClamp: 2,
overflow: "hidden",
display: "-webkit-box",
WebkitBoxOrient: "vertical",
WebkitLineClamp: 2
}}
>
{workspace.description}
@@ -250,7 +241,7 @@ export const ProductOverview = ({ type }: Props) => {
)}
<button type="button">
<div className="group ml-auto w-max cursor-pointer rounded-full border border-mineshaft-600 bg-mineshaft-900 px-4 py-2 text-sm text-mineshaft-300 transition-all hover:border-primary-500/80 hover:bg-primary-800/20 hover:text-mineshaft-200">
Explore{' '}
Explore{" "}
<FontAwesomeIcon
icon={faArrowRight}
className="pl-1.5 pr-0.5 duration-200 hover:pl-2 hover:pr-0"
@@ -259,31 +250,25 @@ export const ProductOverview = ({ type }: Props) => {
</button>
</div>
</div>
)
);
const renderProjectListItem = (
workspace: Workspace,
isFavorite: boolean,
index: number,
) => (
const renderProjectListItem = (workspace: Workspace, isFavorite: boolean, index: number) => (
// eslint-disable-next-line jsx-a11y/no-static-element-interactions, jsx-a11y/click-events-have-key-events
<div
onClick={() => {
// TODO(rbr): fix this
navigate({
to: getProjectHomePage(workspace),
})
localStorage.setItem('projectData.id', workspace.id)
to: getProjectHomePage(workspace)
});
localStorage.setItem("projectData.id", workspace.id);
}}
key={workspace.id}
className={`group grid h-14 min-w-72 cursor-pointer grid-cols-6 border-l border-r border-t border-mineshaft-600 bg-mineshaft-800 px-6 hover:bg-mineshaft-700 ${
index === 0 && 'rounded-t-md'
index === 0 && "rounded-t-md"
}`}
>
<div className="flex items-center sm:col-span-3 lg:col-span-4">
<div className="truncate text-sm text-mineshaft-100">
{workspace.name}
</div>
<div className="truncate text-sm text-mineshaft-100">{workspace.name}</div>
</div>
<div className="flex items-center justify-end sm:col-span-3 lg:col-span-2">
<div className="text-center text-sm text-mineshaft-300">
@@ -294,8 +279,8 @@ export const ProductOverview = ({ type }: Props) => {
icon={faSolidStar}
className="ml-6 text-sm text-yellow-600 hover:text-mineshaft-400"
onClick={(e) => {
e.stopPropagation()
removeProjectFromFavorites(workspace.id)
e.stopPropagation();
removeProjectFromFavorites(workspace.id);
}}
/>
) : (
@@ -303,16 +288,16 @@ export const ProductOverview = ({ type }: Props) => {
icon={faStar}
className="ml-6 text-sm text-mineshaft-400 hover:text-mineshaft-300"
onClick={(e) => {
e.stopPropagation()
addProjectToFavorites(workspace.id)
e.stopPropagation();
addProjectToFavorites(workspace.id);
}}
/>
)}
</div>
</div>
)
);
let projectsComponents: ReactNode
let projectsComponents: ReactNode;
if (filteredWorkspaces.length || isProjectViewLoading) {
switch (projectsViewMode) {
@@ -339,14 +324,14 @@ export const ProductOverview = ({ type }: Props) => {
{!isProjectViewLoading && (
<>
{workspacesWithFaveProp.map((workspace) =>
renderProjectGridItem(workspace, workspace.isFavorite),
renderProjectGridItem(workspace, workspace.isFavorite)
)}
</>
)}
</div>
)
);
break
break;
case ProjectsViewMode.LIST:
default:
projectsComponents = (
@@ -356,19 +341,19 @@ export const ProductOverview = ({ type }: Props) => {
<div
key={`workspace-cards-loading-${i + 1}`}
className={`group flex h-12 min-w-72 cursor-pointer flex-row items-center justify-between border border-mineshaft-600 bg-mineshaft-800 px-6 hover:bg-mineshaft-700 ${
i === 0 && 'rounded-t-md'
} ${i === 2 && 'rounded-b-md border-b'}`}
i === 0 && "rounded-t-md"
} ${i === 2 && "rounded-b-md border-b"}`}
>
<Skeleton className="w-full bg-mineshaft-600" />
</div>
))}
{!isProjectViewLoading &&
workspacesWithFaveProp.map((workspace, ind) =>
renderProjectListItem(workspace, workspace.isFavorite, ind),
renderProjectListItem(workspace, workspace.isFavorite, ind)
)}
</div>
)
break
);
break;
}
} else if (orgWorkspaces.length && searchFilter) {
projectsComponents = (
@@ -377,19 +362,15 @@ export const ProductOverview = ({ type }: Props) => {
icon={faSearch}
className="mb-4 mt-2 w-full text-center text-5xl text-mineshaft-400"
/>
<div className="text-center font-light">
No projects match search...
</div>
<div className="text-center font-light">No projects match search...</div>
</div>
)
);
}
return (
<div className="mx-auto flex max-w-7xl flex-col justify-start bg-bunker-800 md:h-screen">
<Helmet>
<title>
{t('common.head-title', { title: t('settings.members.title') })}
</title>
<title>{t("common.head-title", { title: t("settings.members.title") })}</title>
<link rel="icon" href="/infisical.ico" />
</Helmet>
{!serverDetails?.redisConfigured && (
@@ -400,8 +381,8 @@ export const ProductOverview = ({ type }: Props) => {
icon={faExclamationCircle}
className="mr-4 p-4 text-2xl text-mineshaft-50"
/>
Attention: Updated versions of Infisical now require Redis for full
functionality. Learn how to configure it
Attention: Updated versions of Infisical now require Redis for full functionality. Learn
how to configure it
<a
href="https://infisical.com/docs/self-hosting/configuration/redis"
rel="noopener noreferrer"
@@ -417,9 +398,7 @@ export const ProductOverview = ({ type }: Props) => {
)}
<div className="mb-4 flex flex-col items-start justify-start px-6 py-6 pb-0">
<div className="flex w-full justify-between">
<p className="mr-4 text-3xl font-semibold text-white">
{formatTitle(type)}
</p>
<p className="mr-4 text-3xl font-semibold text-white">{formatTitle(type)}</p>
</div>
<div>
<p className="mr-4 mt-2 text-gray-400">{formatDescription(type)}</p>
@@ -437,9 +416,7 @@ export const ProductOverview = ({ type }: Props) => {
<IconButton
className="min-w-[2.4rem] border-none hover:bg-mineshaft-600"
ariaLabel={`Sort ${
orderDirection === OrderByDirection.ASC
? 'descending'
: 'ascending'
orderDirection === OrderByDirection.ASC ? "descending" : "ascending"
}`}
variant="plain"
size="xs"
@@ -447,11 +424,7 @@ export const ProductOverview = ({ type }: Props) => {
onClick={toggleOrderDirection}
>
<FontAwesomeIcon
icon={
orderDirection === OrderByDirection.ASC
? faArrowDownAZ
: faArrowUpZA
}
icon={orderDirection === OrderByDirection.ASC ? faArrowDownAZ : faArrowUpZA}
/>
</IconButton>
</Tooltip>
@@ -460,15 +433,13 @@ export const ProductOverview = ({ type }: Props) => {
<IconButton
variant="outline_bg"
onClick={() => {
localStorage.setItem('projectsViewMode', ProjectsViewMode.GRID)
setProjectsViewMode(ProjectsViewMode.GRID)
localStorage.setItem("projectsViewMode", ProjectsViewMode.GRID);
setProjectsViewMode(ProjectsViewMode.GRID);
}}
ariaLabel="grid"
size="xs"
className={`${
projectsViewMode === ProjectsViewMode.GRID
? 'bg-mineshaft-500'
: 'bg-transparent'
projectsViewMode === ProjectsViewMode.GRID ? "bg-mineshaft-500" : "bg-transparent"
} min-w-[2.4rem] border-none hover:bg-mineshaft-600`}
>
<FontAwesomeIcon icon={faBorderAll} />
@@ -476,24 +447,19 @@ export const ProductOverview = ({ type }: Props) => {
<IconButton
variant="outline_bg"
onClick={() => {
localStorage.setItem('projectsViewMode', ProjectsViewMode.LIST)
setProjectsViewMode(ProjectsViewMode.LIST)
localStorage.setItem("projectsViewMode", ProjectsViewMode.LIST);
setProjectsViewMode(ProjectsViewMode.LIST);
}}
ariaLabel="list"
size="xs"
className={`${
projectsViewMode === ProjectsViewMode.LIST
? 'bg-mineshaft-500'
: 'bg-transparent'
projectsViewMode === ProjectsViewMode.LIST ? "bg-mineshaft-500" : "bg-transparent"
} min-w-[2.4rem] border-none hover:bg-mineshaft-600`}
>
<FontAwesomeIcon icon={faList} />
</IconButton>
</div>
<OrgPermissionCan
I={OrgPermissionActions.Create}
an={OrgPermissionSubjects.Workspace}
>
<OrgPermissionCan I={OrgPermissionActions.Create} an={OrgPermissionSubjects.Workspace}>
{(isAllowed) => (
<Button
isDisabled={!isAllowed}
@@ -501,9 +467,9 @@ export const ProductOverview = ({ type }: Props) => {
leftIcon={<FontAwesomeIcon icon={faPlus} />}
onClick={() => {
if (isAddingProjectsAllowed) {
handlePopUpOpen('addNewWs')
handlePopUpOpen("addNewWs");
} else {
handlePopUpOpen('upgradePlan')
handlePopUpOpen("upgradePlan");
}
}}
className="ml-2"
@@ -518,8 +484,8 @@ export const ProductOverview = ({ type }: Props) => {
<Pagination
className={
projectsViewMode === ProjectsViewMode.GRID
? 'col-span-full !justify-start border-transparent bg-transparent pl-2'
: 'rounded-b-md border border-mineshaft-600'
? "col-span-full !justify-start border-transparent bg-transparent pl-2"
: "rounded-b-md border border-mineshaft-600"
}
perPage={perPage}
perPageList={[12, 24, 48, 96]}
@@ -536,36 +502,34 @@ export const ProductOverview = ({ type }: Props) => {
className="mb-4 mt-2 w-full text-center text-5xl text-mineshaft-400"
/>
<div className="text-center font-light">
You are not part of any projects in this organization yet. When
you are, they will appear here.
You are not part of any projects in this organization yet. When you are, they will
appear here.
</div>
<div className="mt-0.5 text-center font-light">
Create a new project, or ask other organization members to give
you necessary permissions.
Create a new project, or ask other organization members to give you necessary
permissions.
</div>
</div>
)}
</div>
<NewProjectModal
isOpen={popUp.addNewWs.isOpen}
onOpenChange={(isOpen) => handlePopUpToggle('addNewWs', isOpen)}
onOpenChange={(isOpen) => handlePopUpToggle("addNewWs", isOpen)}
projectType={type}
/>
<UpgradePlanModal
isOpen={popUp.upgradePlan.isOpen}
onOpenChange={(isOpen) => handlePopUpToggle('upgradePlan', isOpen)}
onOpenChange={(isOpen) => handlePopUpToggle("upgradePlan", isOpen)}
text="You have exceeded the number of projects allowed on the free plan."
/>
</div>
)
}
);
};
const SecretManagerOverviewPage = () => (
<ProductOverview type={ProjectType.SecretManager} />
)
const SecretManagerOverviewPage = () => <ProductOverview type={ProjectType.SecretManager} />;
export const Route = createFileRoute(
'/_authenticate/_org_details/_org-layout/organization/$organizationId/secret-manager/overview',
"/_authenticate/_org_details/_org-layout/organization/$organizationId/secret-manager/overview"
)({
component: SecretManagerOverviewPage,
})
component: SecretManagerOverviewPage
});

View File

@@ -1,73 +1,68 @@
import { useEffect } from 'react'
import { Helmet } from 'react-helmet'
import { createFileRoute, useSearch } from '@tanstack/react-router'
import { useEffect } from "react";
import { Helmet } from "react-helmet";
import { createFileRoute, useSearch } from "@tanstack/react-router";
import { zodValidator } from "@tanstack/zod-adapter";
import { z } from "zod";
import { OrgPermissionCan } from '@app/components/permissions'
import { Button, NoticeBanner } from '@app/components/v2'
import { OrgPermissionCan } from "@app/components/permissions";
import { Button, NoticeBanner } from "@app/components/v2";
import {
OrgPermissionActions,
OrgPermissionSubjects,
useOrganization,
useServerConfig,
} from '@app/context'
import { withPermission } from '@app/hoc'
useServerConfig
} from "@app/context";
import { withPermission } from "@app/hoc";
import {
useCreateNewInstallationSession,
useGetSecretScanningInstallationStatus,
useLinkGitAppInstallationWithOrg,
} from '@app/hooks/api/secretScanning'
useLinkGitAppInstallationWithOrg
} from "@app/hooks/api/secretScanning";
import { SecretScanningLogsTable } from './-components'
import { SecretScanningLogsTable } from "./-components";
const SecretScanning = withPermission(
() => {
const queryParams = useSearch({
from: '/_authenticate/_org_details/_org-layout/organization/$organizationId/secret-scanning',
})
const { config } = useServerConfig()
const { currentOrg } = useOrganization()
const organizationId = currentOrg.id
from: "/_authenticate/_org_details/_org-layout/organization/$organizationId/secret-scanning/"
});
const { config } = useServerConfig();
const { currentOrg } = useOrganization();
const organizationId = currentOrg.id;
const { mutateAsync: linkGitAppInstallationWithOrganization } =
useLinkGitAppInstallationWithOrg()
const { mutateAsync: createNewIntegrationSession } =
useCreateNewInstallationSession()
const {
data: installationStatus,
isLoading: isSecretScanningInstatllationStatusLoading,
} = useGetSecretScanningInstallationStatus(organizationId)
useLinkGitAppInstallationWithOrg();
const { mutateAsync: createNewIntegrationSession } = useCreateNewInstallationSession();
const { data: installationStatus, isLoading: isSecretScanningInstatllationStatusLoading } =
useGetSecretScanningInstallationStatus(organizationId);
const integrationEnabled =
!isSecretScanningInstatllationStatusLoading &&
installationStatus?.appInstallationCompleted
!isSecretScanningInstatllationStatusLoading && installationStatus?.appInstallationCompleted;
useEffect(() => {
const linkInstallation = async () => {
if (
typeof queryParams.state === 'string' &&
typeof queryParams.installation_id === 'string'
) {
if (queryParams.state && queryParams.installation_id) {
try {
const isLinked = await linkGitAppInstallationWithOrganization({
installationId: queryParams.installation_id as string,
sessionId: queryParams.state as string,
})
sessionId: queryParams.state as string
});
if (isLinked) {
window.location.reload()
window.location.reload();
}
console.log('installation verification complete')
console.log("installation verification complete");
} catch (e) {
console.log('app installation is stale, start new session', e)
console.log("app installation is stale, start new session", e);
}
}
}
linkInstallation()
}, [queryParams.state, queryParams.installation_id])
};
linkInstallation();
}, [queryParams.state, queryParams.installation_id]);
const generateNewIntegrationSession = async () => {
const session = await createNewIntegrationSession({ organizationId })
window.location.href = `https://github.com/apps/infisical-radar/installations/new?state=${session.sessionId}`
}
const session = await createNewIntegrationSession({ organizationId });
window.location.href = `https://github.com/apps/infisical-radar/installations/new?state=${session.sessionId}`;
};
return (
<div>
@@ -78,26 +73,20 @@ const SecretScanning = withPermission(
</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="mt-6 text-3xl font-semibold text-gray-200">
Secret Scanning
</div>
<div className="mt-6 text-3xl font-semibold text-gray-200">Secret Scanning</div>
<div className="mb-6 text-lg text-mineshaft-300">
Automatically monitor your GitHub activity and prevent secret
leaks
Automatically monitor your GitHub activity and prevent secret leaks
</div>
{config.isSecretScanningDisabled && (
<NoticeBanner
title="Secret scanning is in maintenance"
className="mb-4"
>
We are working on improving the performance of secret scanning
due to increased usage.
<NoticeBanner title="Secret scanning is in maintenance" className="mb-4">
We are working on improving the performance of secret scanning due to increased
usage.
</NoticeBanner>
)}
<div className="relative mb-6 flex justify-between rounded-md border border-mineshaft-600 bg-mineshaft-800 p-6">
<div className="flex flex-col items-start">
<div className="mb-1 flex flex-row">
Secret Scanning Status:{' '}
Secret Scanning Status:{" "}
{integrationEnabled ? (
<p className="ml-1.5 font-semibold text-green">Enabled</p>
) : (
@@ -107,8 +96,8 @@ const SecretScanning = withPermission(
<div>
{integrationEnabled ? (
<p className="text-mineshaft-300">
Your GitHub organization is connected to Infisical, and is
being continuously monitored for secret leaks.
Your GitHub organization is connected to Infisical, and is being continuously
monitored for secret leaks.
</p>
) : (
<p className="text-mineshaft-300">
@@ -148,16 +137,22 @@ const SecretScanning = withPermission(
</div>
</div>
</div>
)
);
},
{
action: OrgPermissionActions.Read,
subject: OrgPermissionSubjects.SecretScanning,
},
)
subject: OrgPermissionSubjects.SecretScanning
}
);
const SecretScanningQueryParams = z.object({
state: z.string().catch(""),
installation_id: z.string().catch("")
});
export const Route = createFileRoute(
'/_authenticate/_org_details/_org-layout/organization/$organizationId/secret-scanning/',
"/_authenticate/_org_details/_org-layout/organization/$organizationId/secret-scanning/"
)({
component: SecretScanning,
})
validateSearch: zodValidator(SecretScanningQueryParams)
});

View File

@@ -1 +1 @@
export { ImportTab } from "./ImportTab";
export { ImportTab } from "./ImportTab";

View File

@@ -3,6 +3,7 @@ import { Controller, useForm } from "react-hook-form";
import { faUsers, faXmark } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { zodResolver } from "@hookform/resolvers/zod";
import { useNavigate } from "@tanstack/react-router";
import { z } from "zod";
import { createNotification } from "@app/components/notifications";
@@ -35,7 +36,6 @@ import {
useGetOrganizationGroups
} from "@app/hooks/api";
import { UsePopUpState } from "@app/hooks/usePopUp";
import { useNavigate } from "@tanstack/react-router";
const schema = z.object({
ldapGroupCN: z.string().min(1, "LDAP Group CN is required"),

View File

@@ -24,7 +24,7 @@ const tabs = [
];
export const OrgTabGroup = () => {
const search = useSearch({
from: "/_authenticate/_org_details/_org-layout/organization/$organizationId/settings"
from: "/_authenticate/_org_details/_org-layout/organization/$organizationId/settings/"
});
const [selectedTab, setSelectedTab] = useState(search.selectedTab || tabs[0].key);

View File

@@ -1,7 +1,6 @@
import { faSlack } from "@fortawesome/free-brands-svg-icons";
import { faEllipsis, faGear, faPlus } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { useNavigate } from "@tanstack/react-router";
import axios from "axios";
import { twMerge } from "tailwind-merge";
@@ -45,7 +44,6 @@ export const OrgWorkflowIntegrationTab = withPermission(
] as const);
const { currentOrg } = useOrganization();
const navigate = useNavigate();
const { data: workflowIntegrations, isLoading: isWorkflowIntegrationsLoading } =
useGetWorkflowIntegrations(currentOrg?.id);

View File

@@ -1,6 +1,7 @@
import { useEffect } from "react";
import { Controller, useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { useNavigate } from "@tanstack/react-router";
import axios from "axios";
import { z } from "zod";
@@ -14,7 +15,6 @@ import {
useUpdateSlackIntegration
} from "@app/hooks/api";
import { slugSchema } from "@app/lib/schemas";
import { useNavigate } from "@tanstack/react-router";
type Props = {
id?: string;

View File

@@ -1,33 +1,29 @@
import { Helmet } from 'react-helmet'
import { useTranslation } from 'react-i18next'
import { createFileRoute } from '@tanstack/react-router'
import { Helmet } from "react-helmet";
import { useTranslation } from "react-i18next";
import { createFileRoute } from "@tanstack/react-router";
import { zodValidator } from "@tanstack/zod-adapter";
import { z } from "zod";
import { OrgTabGroup } from './-components'
import { OrgTabGroup } from "./-components";
const SettingsOrg = () => {
const { t } = useTranslation()
const { t } = useTranslation();
return (
<>
<Helmet>
<title>
{t('common.head-title', { title: t('settings.org.title') })}
</title>
<title>{t("common.head-title", { title: t("settings.org.title") })}</title>
<link rel="icon" href="/infisical.ico" />
</Helmet>
<div className="flex w-full justify-center bg-bunker-800 py-6 text-white">
<div className="w-full max-w-4xl px-6">
<div className="mb-4">
<p className="text-3xl font-semibold text-gray-200">
{t('settings.org.title')}
</p>
<p className="text-3xl font-semibold text-gray-200">{t("settings.org.title")}</p>
</div>
<div className="flex w-full justify-center bg-bunker-800 py-6 text-white">
<div className="w-full max-w-4xl px-6">
<div className="mb-4">
<p className="text-3xl font-semibold text-gray-200">
{t('settings.org.title')}
</p>
<p className="text-3xl font-semibold text-gray-200">{t("settings.org.title")}</p>
</div>
<OrgTabGroup />
</div>
@@ -35,11 +31,16 @@ const SettingsOrg = () => {
</div>
</div>
</>
)
}
);
};
export const SettingsOrgQueryParams = z.object({
selectedTab: z.string().catch("")
});
export const Route = createFileRoute(
'/_authenticate/_org_details/_org-layout/organization/$organizationId/settings/',
"/_authenticate/_org_details/_org-layout/organization/$organizationId/settings/"
)({
component: SettingsOrg,
})
validateSearch: zodValidator(SettingsOrgQueryParams)
});

View File

@@ -1,10 +1,12 @@
import { createFileRoute, redirect } from "@tanstack/react-router";
import { ProjectType } from "@app/hooks/api/workspace/types";
export const Route = createFileRoute("/_authenticate/_org_details/_org-layout/organization/")({
beforeLoad: ({ context }) => {
redirect({
throw: true,
to: "/organization/$organizationId/secret-manager",
to: `/organization/$organizationId/${ProjectType.SecretManager}/overview` as const,
params: {
organizationId: String(context.organizationId)
}

View File

@@ -1,10 +1,10 @@
import { useEffect } from "react";
import { Helmet } from "react-helmet";
import { useTranslation } from "react-i18next";
import { createFileRoute } from "@tanstack/react-router";
import { CreateOrgModal } from "@app/components/organization/CreateOrgModal";
import { usePopUp } from "@app/hooks";
import { createFileRoute } from "@tanstack/react-router";
const NoneOrganization = () => {
const { t } = useTranslation();

View File

@@ -2,6 +2,7 @@ import { createFileRoute, redirect } from "@tanstack/react-router";
import { authKeys, fetchAuthToken } from "@app/hooks/api/auth/queries";
import { setAuthToken } from "@app/hooks/api/reactQuery";
import { ProjectType } from "@app/hooks/api/workspace/types";
export const Route = createFileRoute("/_restrict_login_signup")({
beforeLoad: async ({ context }) => {
@@ -20,7 +21,7 @@ export const Route = createFileRoute("/_restrict_login_signup")({
throw redirect({ to: "/login/select-organization" });
}
throw redirect({
to: "/organization/$organizationId/secret-manager",
to: `/organization/$organizationId/${ProjectType.SecretManager}/overview` as const,
params: {
organizationId: data.organizationId
}

View File

@@ -24,7 +24,7 @@ export const navigateUserToOrg = async (navigate: NavigateFn, organizationId?: s
} else {
// user is not part of any non-auth enforced orgs
localStorage.removeItem("orgData.id");
navigate({ to: "/org/none" });
navigate({ to: "/organization/none" });
}
};

View File

@@ -2,9 +2,11 @@ import { useEffect } from "react";
import { createFileRoute, useSearch } from "@tanstack/react-router";
import SecurityClient from "@app/components/utilities/SecurityClient";
import { z } from "zod";
import { zodValidator } from "@tanstack/zod-adapter";
const LoginProviderSuccess = () => {
const search = useSearch({ from: "/login/provider/success" });
const search = useSearch({ from: "/_restrict_login_signup/login/provider/success" });
useEffect(() => {
SecurityClient.setProviderAuthToken(search.token);
@@ -14,6 +16,11 @@ const LoginProviderSuccess = () => {
return <div />;
};
export const Route = createFileRoute("/_restrict_login_signup/login/provider/success")({
component: LoginProviderSuccess
const LoginProviderSuccessQuerySchema = z.object({
token: z.string()
});
export const Route = createFileRoute("/_restrict_login_signup/login/provider/success")({
component: LoginProviderSuccess,
validateSearch: zodValidator(LoginProviderSuccessQuerySchema)
});

View File

@@ -180,7 +180,7 @@ const SelectOrganizationPage = () => {
// Case: User has no organizations.
// This can happen if the user was previously a member, but the organization was deleted or the user was removed.
if (organizations.data.length === 0) {
navigate({ to: "/org/none" });
navigate({ to: "/organization/none" });
} else if (organizations.data.length === 1) {
if (callbackPort) {
handleCliRedirect();

View File

@@ -2,13 +2,15 @@ import { useEffect, useState } from "react";
import { Helmet } from "react-helmet";
import { useTranslation } from "react-i18next";
import { createFileRoute, Link, useSearch } from "@tanstack/react-router";
import { zodValidator } from "@tanstack/zod-adapter";
import { jwtDecode } from "jwt-decode";
import { z } from "zod";
import { PasswordStep } from "../-components";
const LoginSSOPage = () => {
const { t } = useTranslation();
const search = useSearch({ from: "/login/sso" });
const search = useSearch({ from: "/_restrict_login_signup/login/sso/" });
const token = search.token as string;
const [step, setStep] = useState(0);
const [password, setPassword] = useState("");
@@ -58,6 +60,11 @@ const LoginSSOPage = () => {
);
};
export const Route = createFileRoute("/_restrict_login_signup/login/sso/")({
component: LoginSSOPage
const LoginSSOQueryParamsSchema = z.object({
token: z.string()
});
export const Route = createFileRoute("/_restrict_login_signup/login/sso/")({
component: LoginSSOPage,
validateSearch: zodValidator(LoginSSOQueryParamsSchema)
});

View File

@@ -4,7 +4,9 @@ import { FormEvent, useState } from "react";
import { faCheck, faX } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { createFileRoute, useNavigate, useSearch } from "@tanstack/react-router";
import { zodValidator } from "@tanstack/zod-adapter";
import jsrp from "jsrp";
import { z } from "zod";
import InputField from "@app/components/basic/InputField";
import passwordCheck from "@app/components/utilities/checks/password/PasswordCheck";
@@ -389,6 +391,12 @@ const PasswordResetPage = () => {
);
};
export const Route = createFileRoute("/_restrict_login_signup/password-reset")({
component: PasswordResetPage
const PasswordResetPageQueryParamsSchema = z.object({
token: z.string(),
to: z.string()
});
export const Route = createFileRoute("/_restrict_login_signup/password-reset")({
component: PasswordResetPage,
validateSearch: zodValidator(PasswordResetPageQueryParamsSchema)
});

View File

@@ -6,6 +6,7 @@ import jsrp from "jsrp";
import nacl from "tweetnacl";
import { encodeBase64 } from "tweetnacl-util";
import { Mfa } from "@app/components/auth/Mfa";
import Aes256Gcm from "@app/components/utilities/cryptography/aes-256-gcm";
import { deriveArgonKey } from "@app/components/utilities/cryptography/crypto";
import { saveTokenToLocalStorage } from "@app/components/utilities/saveTokenToLocalStorage";
@@ -16,7 +17,6 @@ import { completeAccountSignup, useSelectOrganization } from "@app/hooks/api/aut
import { MfaMethod } from "@app/hooks/api/auth/types";
import { fetchOrganizations } from "@app/hooks/api/organization/queries";
import ProjectService from "@app/services/ProjectService";
import { Mfa } from "@app/components/auth/Mfa";
// eslint-disable-next-line new-cap
const client = new jsrp.client();

View File

@@ -2,7 +2,9 @@ import { useEffect, useState } from "react";
import { Helmet } from "react-helmet";
import { useTranslation } from "react-i18next";
import { createFileRoute, useSearch } from "@tanstack/react-router";
import { zodValidator } from "@tanstack/zod-adapter";
import { jwtDecode } from "jwt-decode";
import { z } from "zod";
import { BackupPDFStep } from "./-components/BackupPDFStep";
import { EmailConfirmationStep } from "./-components/EmailConfirmationStep";
@@ -10,7 +12,7 @@ import { UserInfoSSOStep } from "./-components/UserInfoSSOStep";
const SignupSSOPage = () => {
const { t } = useTranslation();
const search = useSearch({ from: "/signup/sso/" });
const search = useSearch({ from: "/_restrict_login_signup/signup/sso/" });
const token = search.token as string;
const [step, setStep] = useState(0);
@@ -85,6 +87,11 @@ const SignupSSOPage = () => {
);
};
export const Route = createFileRoute("/_restrict_login_signup/signup/sso/")({
component: SignupSSOPage
const SignupSSOPageQueryParamsSchema = z.object({
token: z.string()
});
export const Route = createFileRoute("/_restrict_login_signup/signup/sso/")({
component: SignupSSOPage,
validateSearch: zodValidator(SignupSSOPageQueryParamsSchema)
});

View File

@@ -7,9 +7,11 @@ import { Helmet } from "react-helmet";
import { faWarning, faXmark } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { createFileRoute, Link, useNavigate, useSearch } from "@tanstack/react-router";
import { zodValidator } from "@tanstack/zod-adapter";
import jsrp from "jsrp";
import nacl from "tweetnacl";
import { encodeBase64 } from "tweetnacl-util";
import { z } from "zod";
import { Mfa } from "@app/components/auth/Mfa";
import InputField from "@app/components/basic/InputField";
@@ -386,7 +388,7 @@ const SignupInvitePage = () => {
setBackupKeyIssued
});
navigate({
to: `/organization/$organizationId/${ProjectType.SecretManager}` as const,
to: `/organization/$organizationId/${ProjectType.SecretManager}/overview` as const,
params: {
organizationId
}
@@ -427,6 +429,13 @@ const SignupInvitePage = () => {
);
};
export const Route = createFileRoute("/_restrict_login_signup/signupinvite")({
component: SignupInvitePage
const SignupInvitePageQueryParamsSchema = z.object({
token: z.string(),
to: z.string(),
organization_id: z.string()
});
export const Route = createFileRoute("/_restrict_login_signup/signupinvite")({
component: SignupInvitePage,
validateSearch: zodValidator(SignupInvitePageQueryParamsSchema)
});