mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-07 22:27:48 +00:00
feat: resolved a lot of ts issues, planning to migrate to v5 tanstack query
This commit is contained in:
Generated
+27
-20
@@ -42,9 +42,10 @@
|
|||||||
"@radix-ui/react-toast": "^1.2.3",
|
"@radix-ui/react-toast": "^1.2.3",
|
||||||
"@radix-ui/react-tooltip": "^1.1.5",
|
"@radix-ui/react-tooltip": "^1.1.5",
|
||||||
"@sindresorhus/slugify": "^2.2.1",
|
"@sindresorhus/slugify": "^2.2.1",
|
||||||
"@tanstack/react-query": "^4.36.1",
|
"@tanstack/react-query": "^5.62.7",
|
||||||
"@tanstack/react-router": "^1.87.9",
|
"@tanstack/react-router": "^1.87.9",
|
||||||
"@tanstack/virtual-file-routes": "^1.87.6",
|
"@tanstack/virtual-file-routes": "^1.87.6",
|
||||||
|
"@tanstack/zod-adapter": "^1.91.0",
|
||||||
"@types/nprogress": "^0.2.3",
|
"@types/nprogress": "^0.2.3",
|
||||||
"@ucast/mongo2js": "^1.3.4",
|
"@ucast/mongo2js": "^1.3.4",
|
||||||
"argon2-browser": "^1.18.0",
|
"argon2-browser": "^1.18.0",
|
||||||
@@ -3628,9 +3629,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@tanstack/query-core": {
|
"node_modules/@tanstack/query-core": {
|
||||||
"version": "4.36.1",
|
"version": "5.62.7",
|
||||||
"resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-4.36.1.tgz",
|
"resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.62.7.tgz",
|
||||||
"integrity": "sha512-DJSilV5+ytBP1FbFcEJovv4rnnm/CokuVvrBEtW/Va9DvuJ3HksbXUJEpI0aV1KtuL4ZoO9AVE6PyNLzF7tLeA==",
|
"integrity": "sha512-fgpfmwatsrUal6V+8EC2cxZIQVl9xvL7qYa03gsdsCy985UTUlS4N+/3hCzwR0PclYDqisca2AqR1BVgJGpUDA==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"funding": {
|
"funding": {
|
||||||
"type": "github",
|
"type": "github",
|
||||||
@@ -3638,30 +3639,19 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@tanstack/react-query": {
|
"node_modules/@tanstack/react-query": {
|
||||||
"version": "4.36.1",
|
"version": "5.62.7",
|
||||||
"resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-4.36.1.tgz",
|
"resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.62.7.tgz",
|
||||||
"integrity": "sha512-y7ySVHFyyQblPl3J3eQBWpXZkliroki3ARnBKsdJchlgt7yJLRDUcf4B8soufgiYt3pEQIkBWBx1N9/ZPIeUWw==",
|
"integrity": "sha512-+xCtP4UAFDTlRTYyEjLx0sRtWyr5GIk7TZjZwBu4YaNahi3Rt2oMyRqfpfVrtwsqY2sayP4iXVCwmC+ZqqFmuw==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@tanstack/query-core": "4.36.1",
|
"@tanstack/query-core": "5.62.7"
|
||||||
"use-sync-external-store": "^1.2.0"
|
|
||||||
},
|
},
|
||||||
"funding": {
|
"funding": {
|
||||||
"type": "github",
|
"type": "github",
|
||||||
"url": "https://github.com/sponsors/tannerlinsley"
|
"url": "https://github.com/sponsors/tannerlinsley"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"react": "^16.8.0 || ^17.0.0 || ^18.0.0",
|
"react": "^18 || ^19"
|
||||||
"react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0",
|
|
||||||
"react-native": "*"
|
|
||||||
},
|
|
||||||
"peerDependenciesMeta": {
|
|
||||||
"react-dom": {
|
|
||||||
"optional": true
|
|
||||||
},
|
|
||||||
"react-native": {
|
|
||||||
"optional": true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@tanstack/react-router": {
|
"node_modules/@tanstack/react-router": {
|
||||||
@@ -3855,6 +3845,23 @@
|
|||||||
"url": "https://github.com/sponsors/tannerlinsley"
|
"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": {
|
"node_modules/@types/argon2-browser": {
|
||||||
"version": "1.18.4",
|
"version": "1.18.4",
|
||||||
"resolved": "https://registry.npmjs.org/@types/argon2-browser/-/argon2-browser-1.18.4.tgz",
|
"resolved": "https://registry.npmjs.org/@types/argon2-browser/-/argon2-browser-1.18.4.tgz",
|
||||||
|
|||||||
@@ -46,9 +46,10 @@
|
|||||||
"@radix-ui/react-toast": "^1.2.3",
|
"@radix-ui/react-toast": "^1.2.3",
|
||||||
"@radix-ui/react-tooltip": "^1.1.5",
|
"@radix-ui/react-tooltip": "^1.1.5",
|
||||||
"@sindresorhus/slugify": "^2.2.1",
|
"@sindresorhus/slugify": "^2.2.1",
|
||||||
"@tanstack/react-query": "^4.36.1",
|
"@tanstack/react-query": "^5.62.7",
|
||||||
"@tanstack/react-router": "^1.87.9",
|
"@tanstack/react-router": "^1.87.9",
|
||||||
"@tanstack/virtual-file-routes": "^1.87.6",
|
"@tanstack/virtual-file-routes": "^1.87.6",
|
||||||
|
"@tanstack/zod-adapter": "^1.91.0",
|
||||||
"@types/nprogress": "^0.2.3",
|
"@types/nprogress": "^0.2.3",
|
||||||
"@ucast/mongo2js": "^1.3.4",
|
"@ucast/mongo2js": "^1.3.4",
|
||||||
"argon2-browser": "^1.18.0",
|
"argon2-browser": "^1.18.0",
|
||||||
|
|||||||
@@ -90,7 +90,12 @@ export default function NavHeader({
|
|||||||
)}
|
)}
|
||||||
<FontAwesomeIcon icon={faAngleRight} className="ml-3 mr-3 text-sm text-gray-400" />
|
<FontAwesomeIcon icon={faAngleRight} className="ml-3 mr-3 text-sm text-gray-400" />
|
||||||
{pageName === "Secrets" ? (
|
{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>
|
<a className="text-sm font-semibold text-primary/80 hover:text-primary">{pageName}</a>
|
||||||
</Link>
|
</Link>
|
||||||
) : (
|
) : (
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
import { FC } from "react";
|
import { FC } from "react";
|
||||||
import { Controller, useForm } from "react-hook-form";
|
import { Controller, useForm } from "react-hook-form";
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
|
import { useNavigate } from "@tanstack/react-router";
|
||||||
import z from "zod";
|
import z from "zod";
|
||||||
|
|
||||||
import { createNotification } from "@app/components/notifications";
|
import { createNotification } from "@app/components/notifications";
|
||||||
import { Button, FormControl, Input, Modal, ModalContent } from "@app/components/v2";
|
import { Button, FormControl, Input, Modal, ModalContent } from "@app/components/v2";
|
||||||
import { useCreateOrg, useSelectOrganization } from "@app/hooks/api";
|
import { useCreateOrg, useSelectOrganization } from "@app/hooks/api";
|
||||||
import { ProjectType } from "@app/hooks/api/workspace/types";
|
import { ProjectType } from "@app/hooks/api/workspace/types";
|
||||||
import { useNavigate } from "@tanstack/react-router";
|
|
||||||
|
|
||||||
const schema = z
|
const schema = z
|
||||||
.object({
|
.object({
|
||||||
@@ -58,7 +58,7 @@ export const CreateOrgModal: FC<CreateOrgModalProps> = ({ isOpen, onClose }) =>
|
|||||||
});
|
});
|
||||||
|
|
||||||
navigate({
|
navigate({
|
||||||
to: `/organization/$organizationId/${ProjectType.SecretManager}` as const,
|
to: `/organization/$organizationId/${ProjectType.SecretManager}/overview` as const,
|
||||||
params: {
|
params: {
|
||||||
organizationId: organization.id
|
organizationId: organization.id
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ const attemptChangePassword = ({ email, currentPassword, newPassword }: Params):
|
|||||||
const clientProof = clientOldPassword.getProof();
|
const clientProof = clientOldPassword.getProof();
|
||||||
|
|
||||||
clientNewPassword.init({ username: email, password: newPassword }, async () => {
|
clientNewPassword.init({ username: email, password: newPassword }, async () => {
|
||||||
clientNewPassword.createVerifier(async (err, result) => {
|
clientNewPassword.createVerifier(async (_err, result) => {
|
||||||
try {
|
try {
|
||||||
const derivedKey = await deriveArgonKey({
|
const derivedKey = await deriveArgonKey({
|
||||||
password: newPassword,
|
password: newPassword,
|
||||||
|
|||||||
@@ -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");
|
|
||||||
};
|
|
||||||
@@ -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;
|
|
||||||
};
|
|
||||||
@@ -39,7 +39,7 @@ export const SecretPathInput = ({
|
|||||||
const workspaceId = currentWorkspace?.id || "";
|
const workspaceId = currentWorkspace?.id || "";
|
||||||
const { folderNames: folders } = useGetFoldersByEnv({
|
const { folderNames: folders } = useGetFoldersByEnv({
|
||||||
path: secretPath,
|
path: secretPath,
|
||||||
environments: [environment || currentWorkspace?.environments?.[0].slug],
|
environments: [environment || currentWorkspace?.environments?.[0].slug || ""],
|
||||||
projectId: workspaceId
|
projectId: workspaceId
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,54 +1,5 @@
|
|||||||
import { createContext, ReactNode } from "react";
|
|
||||||
import { useRouteContext } from "@tanstack/react-router";
|
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 = () => {
|
export const useOrgPermission = () => {
|
||||||
const ctx = useRouteContext({
|
const ctx = useRouteContext({
|
||||||
from: "/_authenticate/_org_details",
|
from: "/_authenticate/_org_details",
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
export { OrgPermissionProvider, useOrgPermission } from "./OrgPermissionContext";
|
export { useOrgPermission } from "./OrgPermissionContext";
|
||||||
export type { TOrgPermission } from "./types";
|
export type { TOrgPermission } from "./types";
|
||||||
export { OrgPermissionActions, OrgPermissionSubjects } from "./types";
|
export { OrgPermissionActions, OrgPermissionSubjects } from "./types";
|
||||||
|
|||||||
@@ -1,40 +1,5 @@
|
|||||||
import { createContext, ReactNode, useContext, useMemo } from "react";
|
|
||||||
import { useRouteContext } from "@tanstack/react-router";
|
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 = () => {
|
export const useOrganization = () => {
|
||||||
const currentOrg = useRouteContext({
|
const currentOrg = useRouteContext({
|
||||||
from: "/_authenticate/_org_details",
|
from: "/_authenticate/_org_details",
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
export { OrgProvider, useOrganization } from "./OrganizationContext";
|
export { useOrganization } from "./OrganizationContext";
|
||||||
|
|||||||
@@ -1,86 +1,11 @@
|
|||||||
import { createContext, ReactNode, useEffect, useMemo } from "react";
|
import { adminQueryKeys, fetchServerConfig } from "@app/hooks/api/admin/queries";
|
||||||
import { Helmet } from "react-helmet";
|
import { useSuspenseQuery } from "@tanstack/react-query";
|
||||||
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>;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const useServerConfig = () => {
|
export const useServerConfig = () => {
|
||||||
const config = useRouteContext({ from: "__root__", select: (el) => el.serverConfig });
|
const { data: config } = useSuspenseQuery({
|
||||||
|
queryKey: adminQueryKeys.serverConfig(),
|
||||||
|
queryFn: fetchServerConfig
|
||||||
|
});
|
||||||
|
|
||||||
return { config };
|
return { config };
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
export { ServerConfigProvider, useServerConfig } from "./ServerConfigContext";
|
export { useServerConfig } from "./ServerConfigContext";
|
||||||
|
|||||||
@@ -1,41 +1,5 @@
|
|||||||
import { createContext, ReactNode, useContext, useMemo } from "react";
|
|
||||||
import { useRouteContext } from "@tanstack/react-router";
|
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 = () => {
|
export const useSubscription = () => {
|
||||||
const subscription = useRouteContext({
|
const subscription = useRouteContext({
|
||||||
from: "/_authenticate/_org_details",
|
from: "/_authenticate/_org_details",
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
export { SubscriptionProvider, useSubscription } from "./SubscriptionContext";
|
export { useSubscription } from "./SubscriptionContext";
|
||||||
|
|||||||
@@ -1,49 +1,5 @@
|
|||||||
import { createContext, ReactNode, useContext, useMemo } from "react";
|
|
||||||
import { useRouteContext } from "@tanstack/react-router";
|
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 = () => {
|
export const useUser = () => {
|
||||||
const user = useRouteContext({ from: "/_authenticate", select: (el) => el.user })!;
|
const user = useRouteContext({ from: "/_authenticate", select: (el) => el.user })!;
|
||||||
|
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
export { UserProvider, useUser } from "./UserContext";
|
export { useUser } from "./UserContext";
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
export { AuthProvider } from "./AuthContext";
|
export { AuthProvider } from "./AuthContext";
|
||||||
export { OrgProvider, useOrganization } from "./OrganizationContext";
|
export { useOrganization } from "./OrganizationContext";
|
||||||
export type { TOrgPermission } from "./OrgPermissionContext";
|
export type { TOrgPermission } from "./OrgPermissionContext";
|
||||||
export {
|
export {
|
||||||
OrgPermissionActions,
|
OrgPermissionActions,
|
||||||
OrgPermissionProvider,
|
|
||||||
OrgPermissionSubjects,
|
OrgPermissionSubjects,
|
||||||
useOrgPermission
|
useOrgPermission
|
||||||
} from "./OrgPermissionContext";
|
} from "./OrgPermissionContext";
|
||||||
@@ -16,7 +15,7 @@ export {
|
|||||||
ProjectPermissionSub,
|
ProjectPermissionSub,
|
||||||
useProjectPermission
|
useProjectPermission
|
||||||
} from "./ProjectPermissionContext";
|
} from "./ProjectPermissionContext";
|
||||||
export { ServerConfigProvider, useServerConfig } from "./ServerConfigContext";
|
export { useServerConfig } from "./ServerConfigContext";
|
||||||
export { SubscriptionProvider, useSubscription } from "./SubscriptionContext";
|
export { useSubscription } from "./SubscriptionContext";
|
||||||
export { UserProvider, useUser } from "./UserContext";
|
export { useUser } from "./UserContext";
|
||||||
export { useWorkspace, WorkspaceProvider } from "./WorkspaceContext";
|
export { useWorkspace, WorkspaceProvider } from "./WorkspaceContext";
|
||||||
|
|||||||
@@ -60,16 +60,15 @@ export const initProjectHelper = async ({ projectName }: { projectName: string }
|
|||||||
|
|
||||||
return project;
|
return project;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getProjectHomePage = (workspace: Workspace) => {
|
export const getProjectHomePage = (workspace: Workspace) => {
|
||||||
if (workspace.type === ProjectType.SecretManager) {
|
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) {
|
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) => {
|
export const getProjectTitle = (type: ProjectType) => {
|
||||||
|
|||||||
@@ -89,10 +89,9 @@ export const useSelectOrganization = () => {
|
|||||||
return data;
|
return data;
|
||||||
},
|
},
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
queryClient.invalidateQueries([
|
queryClient.invalidateQueries({
|
||||||
organizationKeys.getUserOrganizations,
|
queryKey: [organizationKeys.getUserOrganizations, workspaceKeys.getAllUserWorkspace]
|
||||||
workspaceKeys.getAllUserWorkspace
|
});
|
||||||
]);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -21,7 +21,9 @@ export const useImportEnvKey = () => {
|
|||||||
"Content-Type": "multipart/form-data"
|
"Content-Type": "multipart/form-data"
|
||||||
},
|
},
|
||||||
onUploadProgress: (progressEvent) => {
|
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}%`);
|
console.log(`Upload Progress: ${percentCompleted}%`);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -143,7 +143,7 @@ const fetchSecretApprovalRequestList = async ({
|
|||||||
export const useGetSecretApprovalRequests = ({
|
export const useGetSecretApprovalRequests = ({
|
||||||
workspaceId,
|
workspaceId,
|
||||||
environment,
|
environment,
|
||||||
options = object,
|
options = {},
|
||||||
status,
|
status,
|
||||||
limit = 20,
|
limit = 20,
|
||||||
committer
|
committer
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
import { apiRequest } from "@app/config/request";
|
|
||||||
import { useQuery } from "@tanstack/react-query";
|
import { useQuery } from "@tanstack/react-query";
|
||||||
|
|
||||||
|
import { apiRequest } from "@app/config/request";
|
||||||
|
|
||||||
import { TSecretScanningGitRisks } from "./types";
|
import { TSecretScanningGitRisks } from "./types";
|
||||||
|
|
||||||
export const secretScanningQueryKeys = {
|
export const secretScanningQueryKeys = {
|
||||||
|
|||||||
@@ -49,9 +49,10 @@ export const useGetActiveSharedSecretById = ({
|
|||||||
hashedHex: string | null;
|
hashedHex: string | null;
|
||||||
password?: string;
|
password?: string;
|
||||||
}) => {
|
}) => {
|
||||||
return useQuery<TViewSharedSecretResponse>(
|
return useQuery({
|
||||||
secretSharingKeys.getSecretById({ id: sharedSecretId, hashedHex, password }),
|
queryKey: secretSharingKeys.getSecretById({ id: sharedSecretId, hashedHex, password }),
|
||||||
async () => {
|
|
||||||
|
queryFn: async () => {
|
||||||
const { data } = await apiRequest.post<TViewSharedSecretResponse>(
|
const { data } = await apiRequest.post<TViewSharedSecretResponse>(
|
||||||
`/api/v1/secret-sharing/public/${sharedSecretId}`,
|
`/api/v1/secret-sharing/public/${sharedSecretId}`,
|
||||||
{
|
{
|
||||||
@@ -61,9 +62,8 @@ export const useGetActiveSharedSecretById = ({
|
|||||||
);
|
);
|
||||||
|
|
||||||
return data;
|
return data;
|
||||||
},
|
|
||||||
{
|
|
||||||
enabled: Boolean(sharedSecretId)
|
|
||||||
}
|
}
|
||||||
);
|
}, {
|
||||||
|
enabled: Boolean(sharedSecretId)
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -148,14 +148,18 @@ export const usePerformSecretRollback = () => {
|
|||||||
return data;
|
return data;
|
||||||
},
|
},
|
||||||
onSuccess: (_, { workspaceId, environment, directory }) => {
|
onSuccess: (_, { workspaceId, environment, directory }) => {
|
||||||
queryClient.invalidateQueries([
|
queryClient.invalidateQueries({
|
||||||
{ workspaceId, environment, secretPath: directory },
|
queryKey: [
|
||||||
"secrets"
|
{ workspaceId, environment, secretPath: directory },
|
||||||
]);
|
"secrets"
|
||||||
queryClient.invalidateQueries([
|
]
|
||||||
"secret-folders",
|
});
|
||||||
{ projectId: workspaceId, environment, path: directory }
|
queryClient.invalidateQueries({
|
||||||
]);
|
queryKey: [
|
||||||
|
"secret-folders",
|
||||||
|
{ projectId: workspaceId, environment, path: directory }
|
||||||
|
]
|
||||||
|
});
|
||||||
queryClient.invalidateQueries(
|
queryClient.invalidateQueries(
|
||||||
secretSnapshotKeys.list({ workspaceId, environment, directory })
|
secretSnapshotKeys.list({ workspaceId, environment, directory })
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -148,9 +148,12 @@ export const useGetUserWorkspaces = ({
|
|||||||
includeRoles?: boolean;
|
includeRoles?: boolean;
|
||||||
type?: ProjectType | "all";
|
type?: ProjectType | "all";
|
||||||
} = {}) =>
|
} = {}) =>
|
||||||
useQuery(workspaceKeys.getAllUserWorkspace(type || ""), () =>
|
useQuery({
|
||||||
fetchUserWorkspaces(includeRoles, type)
|
queryKey: workspaceKeys.getAllUserWorkspace(type || ""),
|
||||||
);
|
|
||||||
|
queryFn: () =>
|
||||||
|
fetchUserWorkspaces(includeRoles, type)
|
||||||
|
});
|
||||||
|
|
||||||
const fetchUserWorkspaceMemberships = async (orgId: string) => {
|
const fetchUserWorkspaceMemberships = async (orgId: string) => {
|
||||||
const { data } = await apiRequest.get<Record<string, Workspace[]>>(
|
const { data } = await apiRequest.get<Record<string, Workspace[]>>(
|
||||||
@@ -324,7 +327,9 @@ export const useDeleteWorkspace = () => {
|
|||||||
},
|
},
|
||||||
onSuccess: (dto) => {
|
onSuccess: (dto) => {
|
||||||
queryClient.invalidateQueries(workspaceKeys.getAllUserWorkspace(dto.type));
|
queryClient.invalidateQueries(workspaceKeys.getAllUserWorkspace(dto.type));
|
||||||
queryClient.invalidateQueries(["org-admin-projects"]);
|
queryClient.invalidateQueries({
|
||||||
|
queryKey: ["org-admin-projects"]
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -74,7 +74,9 @@ export const OrganizationLayout = () => {
|
|||||||
<div className="px-1">
|
<div className="px-1">
|
||||||
<Menu className="mt-4">
|
<Menu className="mt-4">
|
||||||
<Link
|
<Link
|
||||||
to={`/organization/$organizationId/${ProjectType.SecretManager}` as const}
|
to={
|
||||||
|
`/organization/$organizationId/${ProjectType.SecretManager}/overview` as const
|
||||||
|
}
|
||||||
params={{ organizationId: currentOrg.id }}
|
params={{ organizationId: currentOrg.id }}
|
||||||
activeOptions={{ exact: true }}
|
activeOptions={{ exact: true }}
|
||||||
>
|
>
|
||||||
@@ -86,7 +88,7 @@ export const OrganizationLayout = () => {
|
|||||||
</Link>
|
</Link>
|
||||||
<Link
|
<Link
|
||||||
to={
|
to={
|
||||||
`/organization/$organizationid/${ProjectType.CertificateManager}` as const
|
`/organization/$organizationId/${ProjectType.CertificateManager}/overview` as const
|
||||||
}
|
}
|
||||||
params={{ organizationId: currentOrg.id }}
|
params={{ organizationId: currentOrg.id }}
|
||||||
activeOptions={{ exact: true }}
|
activeOptions={{ exact: true }}
|
||||||
@@ -98,7 +100,7 @@ export const OrganizationLayout = () => {
|
|||||||
)}
|
)}
|
||||||
</Link>
|
</Link>
|
||||||
<Link
|
<Link
|
||||||
to={`/organization/$organizationId/${ProjectType.KMS}` as const}
|
to={`/organization/$organizationId/${ProjectType.KMS}/overview` as const}
|
||||||
params={{ organizationId: currentOrg.id }}
|
params={{ organizationId: currentOrg.id }}
|
||||||
activeOptions={{ exact: true }}
|
activeOptions={{ exact: true }}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -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]">
|
<nav className="items-between flex h-full flex-col justify-between overflow-y-auto dark:[color-scheme:dark]">
|
||||||
<div className="flex-grow">
|
<div className="flex-grow">
|
||||||
<Link
|
<Link
|
||||||
to={`/organization/$organizationId/${ProjectType.SecretManager}` as const}
|
to={
|
||||||
|
`/organization/$organizationId/${ProjectType.SecretManager}/overview` as const
|
||||||
|
}
|
||||||
params={{ organizationId }}
|
params={{ organizationId }}
|
||||||
>
|
>
|
||||||
<div className="my-6 flex cursor-default items-center justify-center pr-2 text-sm text-mineshaft-300 hover:text-mineshaft-100">
|
<div className="my-6 flex cursor-default items-center justify-center pr-2 text-sm text-mineshaft-300 hover:text-mineshaft-100">
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ const RootPage = () => {
|
|||||||
export const Route = createRootRouteWithContext<TRouterContext>()({
|
export const Route = createRootRouteWithContext<TRouterContext>()({
|
||||||
component: RootPage,
|
component: RootPage,
|
||||||
beforeLoad: async ({ context }) => {
|
beforeLoad: async ({ context }) => {
|
||||||
const serverConfig = await context.queryClient.fetchQuery({
|
const serverConfig = await context.queryClient.ensureQueryData({
|
||||||
queryKey: adminQueryKeys.serverConfig(),
|
queryKey: adminQueryKeys.serverConfig(),
|
||||||
queryFn: fetchServerConfig
|
queryFn: fetchServerConfig
|
||||||
});
|
});
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,7 @@
|
|||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { faEllipsis, faMagnifyingGlass, faSignIn } from "@fortawesome/free-solid-svg-icons";
|
import { faEllipsis, faMagnifyingGlass, faSignIn } from "@fortawesome/free-solid-svg-icons";
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
|
import { useNavigate } from "@tanstack/react-router";
|
||||||
import { format } from "date-fns";
|
import { format } from "date-fns";
|
||||||
import { motion } from "framer-motion";
|
import { motion } from "framer-motion";
|
||||||
|
|
||||||
@@ -31,7 +32,6 @@ import {
|
|||||||
import { withPermission } from "@app/hoc";
|
import { withPermission } from "@app/hoc";
|
||||||
import { useDebounce } from "@app/hooks";
|
import { useDebounce } from "@app/hooks";
|
||||||
import { useOrgAdminAccessProject, useOrgAdminGetProjects } from "@app/hooks/api";
|
import { useOrgAdminAccessProject, useOrgAdminGetProjects } from "@app/hooks/api";
|
||||||
import { useNavigate } from "@tanstack/react-router";
|
|
||||||
import { ProjectType } from "@app/hooks/api/workspace/types";
|
import { ProjectType } from "@app/hooks/api/workspace/types";
|
||||||
|
|
||||||
export const OrgAdminProjects = withPermission(
|
export const OrgAdminProjects = withPermission(
|
||||||
|
|||||||
+17
-24
@@ -1,38 +1,31 @@
|
|||||||
import { useState } from 'react'
|
import { useState } from "react";
|
||||||
import { Helmet } from 'react-helmet'
|
import { Helmet } from "react-helmet";
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from "react-i18next";
|
||||||
import { createFileRoute } from '@tanstack/react-router'
|
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 {
|
enum TabSections {
|
||||||
Projects = 'projects',
|
Projects = "projects"
|
||||||
}
|
}
|
||||||
|
|
||||||
const OrgAdminPage = () => {
|
const OrgAdminPage = () => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation();
|
||||||
const [activeTab, setActiveTab] = useState<TabSections>(TabSections.Projects)
|
const [activeTab, setActiveTab] = useState<TabSections>(TabSections.Projects);
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Helmet>
|
<Helmet>
|
||||||
<title>
|
<title>{t("common.head-title", { title: t("settings.org.title") })}</title>
|
||||||
{t('common.head-title', { title: t('settings.org.title') })}
|
|
||||||
</title>
|
|
||||||
<link rel="icon" href="/infisical.ico" />
|
<link rel="icon" href="/infisical.ico" />
|
||||||
</Helmet>
|
</Helmet>
|
||||||
<div className="flex w-full justify-center bg-bunker-800 py-6 text-white">
|
<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="w-full max-w-6xl px-6">
|
||||||
<div className="mb-4">
|
<div className="mb-4">
|
||||||
<p className="text-3xl font-semibold text-gray-200">
|
<p className="text-3xl font-semibold text-gray-200">Organization Admin Console</p>
|
||||||
Organization Admin Console
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
<Tabs
|
<Tabs value={activeTab} onValueChange={(el) => setActiveTab(el as TabSections)}>
|
||||||
value={activeTab}
|
|
||||||
onValueChange={(el) => setActiveTab(el as TabSections)}
|
|
||||||
>
|
|
||||||
<TabList>
|
<TabList>
|
||||||
<Tab value={TabSections.Projects}>Projects</Tab>
|
<Tab value={TabSections.Projects}>Projects</Tab>
|
||||||
</TabList>
|
</TabList>
|
||||||
@@ -43,11 +36,11 @@ const OrgAdminPage = () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
)
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
export const Route = createFileRoute(
|
export const Route = createFileRoute(
|
||||||
'/_authenticate/_org_details/_org-layout/organization/$organizationId/admin/',
|
"/_authenticate/_org_details/_org-layout/organization/$organizationId/admin/"
|
||||||
)({
|
)({
|
||||||
component: OrgAdminPage,
|
component: OrgAdminPage
|
||||||
})
|
});
|
||||||
|
|||||||
+19
-21
@@ -1,52 +1,50 @@
|
|||||||
import { Helmet } from 'react-helmet'
|
import { Helmet } from "react-helmet";
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from "react-i18next";
|
||||||
import { createFileRoute } from '@tanstack/react-router'
|
import { createFileRoute } from "@tanstack/react-router";
|
||||||
|
|
||||||
import { OrgPermissionActions, OrgPermissionSubjects } from '@app/context'
|
import { OrgPermissionActions, OrgPermissionSubjects } from "@app/context";
|
||||||
import { withPermission } from '@app/hoc'
|
import { withPermission } from "@app/hoc";
|
||||||
|
|
||||||
import { BillingTabGroup } from './-components'
|
import { BillingTabGroup } from "./-components";
|
||||||
|
|
||||||
const BillngPage = withPermission(
|
const BillngPage = withPermission(
|
||||||
() => {
|
() => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation();
|
||||||
return (
|
return (
|
||||||
<div className="flex h-full w-full justify-center bg-bunker-800 text-white">
|
<div className="flex h-full w-full justify-center bg-bunker-800 text-white">
|
||||||
<div className="w-full max-w-7xl px-6">
|
<div className="w-full max-w-7xl px-6">
|
||||||
<div className="my-6">
|
<div className="my-6">
|
||||||
<p className="text-3xl font-semibold text-gray-200">
|
<p className="text-3xl font-semibold text-gray-200">{t("billing.title")}</p>
|
||||||
{t('billing.title')}
|
|
||||||
</p>
|
|
||||||
<div />
|
<div />
|
||||||
</div>
|
</div>
|
||||||
<BillingTabGroup />
|
<BillingTabGroup />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
);
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
action: OrgPermissionActions.Read,
|
action: OrgPermissionActions.Read,
|
||||||
subject: OrgPermissionSubjects.Billing,
|
subject: OrgPermissionSubjects.Billing
|
||||||
},
|
}
|
||||||
)
|
);
|
||||||
|
|
||||||
const BillingRoute = () => {
|
const BillingRoute = () => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="h-full bg-bunker-800">
|
<div className="h-full bg-bunker-800">
|
||||||
<Helmet>
|
<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" />
|
<link rel="icon" href="/infisical.ico" />
|
||||||
<meta property="og:image" content="/images/message.png" />
|
<meta property="og:image" content="/images/message.png" />
|
||||||
</Helmet>
|
</Helmet>
|
||||||
<BillngPage />
|
<BillngPage />
|
||||||
</div>
|
</div>
|
||||||
)
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
export const Route = createFileRoute(
|
export const Route = createFileRoute(
|
||||||
'/_authenticate/_org_details/_org-layout/organization/$organizationId/billing/',
|
"/_authenticate/_org_details/_org-layout/organization/$organizationId/billing/"
|
||||||
)({
|
)({
|
||||||
component: BillingRoute,
|
component: BillingRoute
|
||||||
})
|
});
|
||||||
|
|||||||
+83
-108
@@ -1,12 +1,12 @@
|
|||||||
import { Helmet } from 'react-helmet'
|
import { Helmet } from "react-helmet";
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from "react-i18next";
|
||||||
import { faChevronLeft, faEllipsis } from '@fortawesome/free-solid-svg-icons'
|
import { faChevronLeft, faEllipsis } from "@fortawesome/free-solid-svg-icons";
|
||||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
import { createFileRoute, useNavigate, useParams } from '@tanstack/react-router'
|
import { createFileRoute, useNavigate, useParams } from "@tanstack/react-router";
|
||||||
import { twMerge } from 'tailwind-merge'
|
import { twMerge } from "tailwind-merge";
|
||||||
|
|
||||||
import { createNotification } from '@app/components/notifications'
|
import { createNotification } from "@app/components/notifications";
|
||||||
import { OrgPermissionCan } from '@app/components/permissions'
|
import { OrgPermissionCan } from "@app/components/permissions";
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
DeleteActionModal,
|
DeleteActionModal,
|
||||||
@@ -16,81 +16,74 @@ import {
|
|||||||
DropdownMenuTrigger,
|
DropdownMenuTrigger,
|
||||||
Spinner,
|
Spinner,
|
||||||
Tooltip,
|
Tooltip,
|
||||||
UpgradePlanModal,
|
UpgradePlanModal
|
||||||
} from '@app/components/v2'
|
} from "@app/components/v2";
|
||||||
import {
|
import { OrgPermissionActions, OrgPermissionSubjects, useOrganization } from "@app/context";
|
||||||
OrgPermissionActions,
|
import { withPermission } from "@app/hoc";
|
||||||
OrgPermissionSubjects,
|
import { useDeleteGroup } from "@app/hooks/api";
|
||||||
useOrganization,
|
import { useGetGroupById } from "@app/hooks/api/groups/queries";
|
||||||
} from '@app/context'
|
import { usePopUp } from "@app/hooks/usePopUp";
|
||||||
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 { GroupCreateUpdateModal } from "./-components/GroupCreateUpdateModal";
|
||||||
import { GroupMembersSection } from './-components/GroupMembersSection'
|
import { GroupMembersSection } from "./-components/GroupMembersSection";
|
||||||
import { GroupDetailsSection } from './-components'
|
import { GroupDetailsSection } from "./-components";
|
||||||
|
|
||||||
export enum TabSections {
|
export enum TabSections {
|
||||||
Member = 'members',
|
Member = "members",
|
||||||
Groups = 'groups',
|
Groups = "groups",
|
||||||
Roles = 'roles',
|
Roles = "roles",
|
||||||
Identities = 'identities',
|
Identities = "identities"
|
||||||
}
|
}
|
||||||
|
|
||||||
const GroupPage = withPermission(
|
const GroupPage = withPermission(
|
||||||
() => {
|
() => {
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate();
|
||||||
const params = useParams({
|
const params = useParams({
|
||||||
from: '/_authenticate/_org_details/_org-layout/organization/$organizationId/groups/$groupId',
|
from: "/_authenticate/_org_details/_org-layout/organization/$organizationId/groups/$groupId/"
|
||||||
})
|
});
|
||||||
const groupId = params.groupId as string
|
const groupId = params.groupId as string;
|
||||||
const { currentOrg } = useOrganization()
|
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 } =
|
const { popUp, handlePopUpOpen, handlePopUpClose, handlePopUpToggle } = usePopUp([
|
||||||
usePopUp(['groupCreateUpdate', 'deleteGroup', 'upgradePlan'] as const)
|
"groupCreateUpdate",
|
||||||
|
"deleteGroup",
|
||||||
|
"upgradePlan"
|
||||||
|
] as const);
|
||||||
|
|
||||||
const onDeleteGroupSubmit = async ({
|
const onDeleteGroupSubmit = async ({ name, id }: { name: string; id: string }) => {
|
||||||
name,
|
|
||||||
id,
|
|
||||||
}: {
|
|
||||||
name: string
|
|
||||||
id: string
|
|
||||||
}) => {
|
|
||||||
try {
|
try {
|
||||||
await deleteMutateAsync({
|
await deleteMutateAsync({
|
||||||
id,
|
id
|
||||||
})
|
});
|
||||||
createNotification({
|
createNotification({
|
||||||
text: `Successfully deleted the ${name} group`,
|
text: `Successfully deleted the ${name} group`,
|
||||||
type: 'success',
|
type: "success"
|
||||||
})
|
});
|
||||||
navigate({
|
navigate({
|
||||||
to: '/organization/$organizationId/members' as const,
|
to: "/organization/$organizationId/members" as const,
|
||||||
params: {
|
params: {
|
||||||
organizationId: currentOrg.id,
|
organizationId: currentOrg.id
|
||||||
},
|
},
|
||||||
search: {
|
search: {
|
||||||
selectedTab: TabSections.Groups,
|
selectedTab: TabSections.Groups
|
||||||
},
|
}
|
||||||
})
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(err)
|
console.error(err);
|
||||||
createNotification({
|
createNotification({
|
||||||
text: `Failed to delete the ${name} group`,
|
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 (
|
return (
|
||||||
<div className="container mx-auto flex flex-col justify-between bg-bunker-800 text-white">
|
<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} />}
|
leftIcon={<FontAwesomeIcon icon={faChevronLeft} />}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
navigate({
|
navigate({
|
||||||
to: '/organization/$organizationId/members' as const,
|
to: "/organization/$organizationId/members" as const,
|
||||||
params: {
|
params: {
|
||||||
organizationId: currentOrg.id,
|
organizationId: currentOrg.id
|
||||||
},
|
},
|
||||||
search: {
|
search: {
|
||||||
selectedTab: TabSections.Groups,
|
selectedTab: TabSections.Groups
|
||||||
},
|
}
|
||||||
})
|
});
|
||||||
}}
|
}}
|
||||||
className="mb-4"
|
className="mb-4"
|
||||||
>
|
>
|
||||||
Groups
|
Groups
|
||||||
</Button>
|
</Button>
|
||||||
<div className="mb-4 flex items-center justify-between">
|
<div className="mb-4 flex items-center justify-between">
|
||||||
<p className="text-3xl font-semibold text-white">
|
<p className="text-3xl font-semibold text-white">{data.group.name}</p>
|
||||||
{data.group.name}
|
|
||||||
</p>
|
|
||||||
<DropdownMenu>
|
<DropdownMenu>
|
||||||
<DropdownMenuTrigger asChild className="rounded-lg">
|
<DropdownMenuTrigger asChild className="rounded-lg">
|
||||||
<div className="hover:text-primary-400 data-[state=open]:text-primary-400">
|
<div className="hover:text-primary-400 data-[state=open]:text-primary-400">
|
||||||
@@ -128,23 +119,19 @@ const GroupPage = withPermission(
|
|||||||
</div>
|
</div>
|
||||||
</DropdownMenuTrigger>
|
</DropdownMenuTrigger>
|
||||||
<DropdownMenuContent align="start" className="p-1">
|
<DropdownMenuContent align="start" className="p-1">
|
||||||
<OrgPermissionCan
|
<OrgPermissionCan I={OrgPermissionActions.Edit} a={OrgPermissionSubjects.Groups}>
|
||||||
I={OrgPermissionActions.Edit}
|
|
||||||
a={OrgPermissionSubjects.Groups}
|
|
||||||
>
|
|
||||||
{(isAllowed) => (
|
{(isAllowed) => (
|
||||||
<DropdownMenuItem
|
<DropdownMenuItem
|
||||||
className={twMerge(
|
className={twMerge(
|
||||||
!isAllowed &&
|
!isAllowed && "pointer-events-none cursor-not-allowed opacity-50"
|
||||||
'pointer-events-none cursor-not-allowed opacity-50',
|
|
||||||
)}
|
)}
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
handlePopUpOpen('groupCreateUpdate', {
|
handlePopUpOpen("groupCreateUpdate", {
|
||||||
groupId,
|
groupId,
|
||||||
name: data.group.name,
|
name: data.group.name,
|
||||||
slug: data.group.slug,
|
slug: data.group.slug,
|
||||||
role: data.group.role,
|
role: data.group.role
|
||||||
})
|
});
|
||||||
}}
|
}}
|
||||||
disabled={!isAllowed}
|
disabled={!isAllowed}
|
||||||
>
|
>
|
||||||
@@ -160,14 +147,14 @@ const GroupPage = withPermission(
|
|||||||
<DropdownMenuItem
|
<DropdownMenuItem
|
||||||
className={twMerge(
|
className={twMerge(
|
||||||
isAllowed
|
isAllowed
|
||||||
? 'hover:!bg-red-500 hover:!text-white'
|
? "hover:!bg-red-500 hover:!text-white"
|
||||||
: 'pointer-events-none cursor-not-allowed opacity-50',
|
: "pointer-events-none cursor-not-allowed opacity-50"
|
||||||
)}
|
)}
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
handlePopUpOpen('deleteGroup', {
|
handlePopUpOpen("deleteGroup", {
|
||||||
id: groupId,
|
id: groupId,
|
||||||
name: data.group.name,
|
name: data.group.name
|
||||||
})
|
});
|
||||||
}}
|
}}
|
||||||
disabled={!isAllowed}
|
disabled={!isAllowed}
|
||||||
>
|
>
|
||||||
@@ -180,15 +167,9 @@ const GroupPage = withPermission(
|
|||||||
</div>
|
</div>
|
||||||
<div className="flex">
|
<div className="flex">
|
||||||
<div className="mr-4 w-96">
|
<div className="mr-4 w-96">
|
||||||
<GroupDetailsSection
|
<GroupDetailsSection groupId={groupId} handlePopUpOpen={handlePopUpOpen} />
|
||||||
groupId={groupId}
|
|
||||||
handlePopUpOpen={handlePopUpOpen}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<GroupMembersSection
|
<GroupMembersSection groupId={groupId} groupSlug={data.group.slug} />
|
||||||
groupId={groupId}
|
|
||||||
groupSlug={data.group.slug}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -200,46 +181,40 @@ const GroupPage = withPermission(
|
|||||||
<DeleteActionModal
|
<DeleteActionModal
|
||||||
isOpen={popUp.deleteGroup.isOpen}
|
isOpen={popUp.deleteGroup.isOpen}
|
||||||
title={`Are you sure want to delete the group named ${
|
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"
|
deleteKey="confirm"
|
||||||
onDeleteApproved={() =>
|
onDeleteApproved={() =>
|
||||||
onDeleteGroupSubmit(
|
onDeleteGroupSubmit(popUp?.deleteGroup?.data as { name: string; id: string })
|
||||||
popUp?.deleteGroup?.data as { name: string; id: string },
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<UpgradePlanModal
|
<UpgradePlanModal
|
||||||
isOpen={popUp.upgradePlan.isOpen}
|
isOpen={popUp.upgradePlan.isOpen}
|
||||||
onOpenChange={(isOpen) => handlePopUpToggle('upgradePlan', isOpen)}
|
onOpenChange={(isOpen) => handlePopUpToggle("upgradePlan", isOpen)}
|
||||||
text={
|
text={(popUp.upgradePlan?.data as { description: string })?.description}
|
||||||
(popUp.upgradePlan?.data as { description: string })?.description
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)
|
);
|
||||||
},
|
},
|
||||||
{ action: OrgPermissionActions.Read, subject: OrgPermissionSubjects.Groups },
|
{ action: OrgPermissionActions.Read, subject: OrgPermissionSubjects.Groups }
|
||||||
)
|
);
|
||||||
|
|
||||||
const GroupDetailPage = () => {
|
const GroupDetailPage = () => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation();
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Helmet>
|
<Helmet>
|
||||||
<title>
|
<title>{t("common.head-title", { title: t("settings.org.title") })}</title>
|
||||||
{t('common.head-title', { title: t('settings.org.title') })}
|
|
||||||
</title>
|
|
||||||
<link rel="icon" href="/infisical.ico" />
|
<link rel="icon" href="/infisical.ico" />
|
||||||
</Helmet>
|
</Helmet>
|
||||||
<GroupPage />
|
<GroupPage />
|
||||||
</>
|
</>
|
||||||
)
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
export const Route = createFileRoute(
|
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
|
||||||
})
|
});
|
||||||
|
|||||||
+150
-178
@@ -1,14 +1,14 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||||
import { useState } from 'react'
|
import { useState } from "react";
|
||||||
import { Helmet } from 'react-helmet'
|
import { Helmet } from "react-helmet";
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from "react-i18next";
|
||||||
import { faChevronLeft, faEllipsis } from '@fortawesome/free-solid-svg-icons'
|
import { faChevronLeft, faEllipsis } from "@fortawesome/free-solid-svg-icons";
|
||||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
import { createFileRoute, useNavigate, useParams } from '@tanstack/react-router'
|
import { createFileRoute, useNavigate, useParams } from "@tanstack/react-router";
|
||||||
import { twMerge } from 'tailwind-merge'
|
import { twMerge } from "tailwind-merge";
|
||||||
|
|
||||||
import { createNotification } from '@app/components/notifications'
|
import { createNotification } from "@app/components/notifications";
|
||||||
import { OrgPermissionCan } from '@app/components/permissions'
|
import { OrgPermissionCan } from "@app/components/permissions";
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
DeleteActionModal,
|
DeleteActionModal,
|
||||||
@@ -17,168 +17,155 @@ import {
|
|||||||
DropdownMenuItem,
|
DropdownMenuItem,
|
||||||
DropdownMenuTrigger,
|
DropdownMenuTrigger,
|
||||||
Tooltip,
|
Tooltip,
|
||||||
UpgradePlanModal,
|
UpgradePlanModal
|
||||||
} from '@app/components/v2'
|
} from "@app/components/v2";
|
||||||
import {
|
import { OrgPermissionActions, OrgPermissionSubjects, useOrganization } from "@app/context";
|
||||||
OrgPermissionActions,
|
import { withPermission } from "@app/hoc";
|
||||||
OrgPermissionSubjects,
|
|
||||||
useOrganization,
|
|
||||||
} from '@app/context'
|
|
||||||
import { withPermission } from '@app/hoc'
|
|
||||||
import {
|
import {
|
||||||
IdentityAuthMethod,
|
IdentityAuthMethod,
|
||||||
useDeleteIdentity,
|
useDeleteIdentity,
|
||||||
useGetIdentityById,
|
useGetIdentityById,
|
||||||
useRevokeIdentityTokenAuthToken,
|
useRevokeIdentityTokenAuthToken,
|
||||||
useRevokeIdentityUniversalAuthClientSecret,
|
useRevokeIdentityUniversalAuthClientSecret
|
||||||
} from '@app/hooks/api'
|
} from "@app/hooks/api";
|
||||||
import { Identity } from '@app/hooks/api/identities/types'
|
import { Identity } from "@app/hooks/api/identities/types";
|
||||||
import { usePopUp } from '@app/hooks/usePopUp'
|
import { usePopUp } from "@app/hooks/usePopUp";
|
||||||
import { TabSections } from '@app/types/org'
|
import { TabSections } from "@app/types/org";
|
||||||
|
|
||||||
import { IdentityAuthMethodModal } from '../../members/-components/OrgIdentityTab/components/IdentitySection/IdentityAuthMethodModal'
|
import { IdentityAuthMethodModal } from "../../members/-components/OrgIdentityTab/components/IdentitySection/IdentityAuthMethodModal";
|
||||||
import { IdentityModal } from '../../members/-components/OrgIdentityTab/components/IdentitySection/IdentityModal'
|
import { IdentityModal } from "../../members/-components/OrgIdentityTab/components/IdentitySection/IdentityModal";
|
||||||
import { IdentityUniversalAuthClientSecretModal } from '../../members/-components/OrgIdentityTab/components/IdentitySection/IdentityUniversalAuthClientSecretModal'
|
import { IdentityUniversalAuthClientSecretModal } from "../../members/-components/OrgIdentityTab/components/IdentitySection/IdentityUniversalAuthClientSecretModal";
|
||||||
import {
|
import {
|
||||||
IdentityAuthenticationSection,
|
IdentityAuthenticationSection,
|
||||||
IdentityClientSecretModal,
|
IdentityClientSecretModal,
|
||||||
IdentityDetailsSection,
|
IdentityDetailsSection,
|
||||||
IdentityProjectsSection,
|
IdentityProjectsSection,
|
||||||
IdentityTokenListModal,
|
IdentityTokenListModal,
|
||||||
IdentityTokenModal,
|
IdentityTokenModal
|
||||||
} from './-components'
|
} from "./-components";
|
||||||
|
|
||||||
export const IdentitySection = withPermission(
|
export const IdentitySection = withPermission(
|
||||||
() => {
|
() => {
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate();
|
||||||
const params = useParams({
|
const params = useParams({
|
||||||
from: '/_authenticate/_org_details/_org-layout/organization/$organizationId/identities/$identityId',
|
from: "/_authenticate/_org_details/_org-layout/organization/$organizationId/identities/$identityId/"
|
||||||
})
|
});
|
||||||
const identityId = params.identityId as string
|
const identityId = params.identityId as string;
|
||||||
const { currentOrg } = useOrganization()
|
const { currentOrg } = useOrganization();
|
||||||
const orgId = currentOrg?.id || ''
|
const orgId = currentOrg?.id || "";
|
||||||
const { data } = useGetIdentityById(identityId)
|
const { data } = useGetIdentityById(identityId);
|
||||||
const { mutateAsync: deleteIdentity } = useDeleteIdentity()
|
const { mutateAsync: deleteIdentity } = useDeleteIdentity();
|
||||||
const { mutateAsync: revokeToken } = useRevokeIdentityTokenAuthToken()
|
const { mutateAsync: revokeToken } = useRevokeIdentityTokenAuthToken();
|
||||||
const { mutateAsync: revokeClientSecret } =
|
const { mutateAsync: revokeClientSecret } = useRevokeIdentityUniversalAuthClientSecret();
|
||||||
useRevokeIdentityUniversalAuthClientSecret()
|
|
||||||
|
|
||||||
const [selectedAuthMethod, setSelectedAuthMethod] = useState<
|
const [selectedAuthMethod, setSelectedAuthMethod] = useState<
|
||||||
Identity['authMethods'][number] | null
|
Identity["authMethods"][number] | null
|
||||||
>(null)
|
>(null);
|
||||||
|
|
||||||
const { popUp, handlePopUpOpen, handlePopUpClose, handlePopUpToggle } =
|
const { popUp, handlePopUpOpen, handlePopUpClose, handlePopUpToggle } = usePopUp([
|
||||||
usePopUp([
|
"identity",
|
||||||
'identity',
|
"deleteIdentity",
|
||||||
'deleteIdentity',
|
"identityAuthMethod",
|
||||||
'identityAuthMethod',
|
"revokeAuthMethod",
|
||||||
'revokeAuthMethod',
|
"token",
|
||||||
'token',
|
"tokenList",
|
||||||
'tokenList',
|
"revokeToken",
|
||||||
'revokeToken',
|
"clientSecret",
|
||||||
'clientSecret',
|
"revokeClientSecret",
|
||||||
'revokeClientSecret',
|
"universalAuthClientSecret", // list of client secrets
|
||||||
'universalAuthClientSecret', // list of client secrets
|
"upgradePlan"
|
||||||
'upgradePlan',
|
] as const);
|
||||||
] as const)
|
|
||||||
|
|
||||||
const onDeleteIdentitySubmit = async (id: string) => {
|
const onDeleteIdentitySubmit = async (id: string) => {
|
||||||
try {
|
try {
|
||||||
await deleteIdentity({
|
await deleteIdentity({
|
||||||
identityId: id,
|
identityId: id,
|
||||||
organizationId: orgId,
|
organizationId: orgId
|
||||||
})
|
});
|
||||||
|
|
||||||
createNotification({
|
createNotification({
|
||||||
text: 'Successfully deleted identity',
|
text: "Successfully deleted identity",
|
||||||
type: 'success',
|
type: "success"
|
||||||
})
|
});
|
||||||
|
|
||||||
handlePopUpClose('deleteIdentity')
|
handlePopUpClose("deleteIdentity");
|
||||||
navigate({
|
navigate({
|
||||||
to: `/organization/${orgId}/members` as const,
|
to: "/organization/$organizationId/members",
|
||||||
search: {
|
params: {
|
||||||
selectedTab: TabSections.Identities,
|
organizationId: orgId
|
||||||
},
|
},
|
||||||
})
|
search: {
|
||||||
|
selectedTab: TabSections.Identities
|
||||||
|
}
|
||||||
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(err)
|
console.error(err);
|
||||||
const error = err as any
|
const error = err as any;
|
||||||
const text =
|
const text = error?.response?.data?.message ?? "Failed to delete identity";
|
||||||
error?.response?.data?.message ?? 'Failed to delete identity'
|
|
||||||
|
|
||||||
createNotification({
|
createNotification({
|
||||||
text,
|
text,
|
||||||
type: 'error',
|
type: "error"
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
const onRevokeTokenSubmit = async ({
|
const onRevokeTokenSubmit = async ({
|
||||||
identityId: parentIdentityId,
|
identityId: parentIdentityId,
|
||||||
tokenId,
|
tokenId,
|
||||||
name,
|
name
|
||||||
}: {
|
}: {
|
||||||
identityId: string
|
identityId: string;
|
||||||
tokenId: string
|
tokenId: string;
|
||||||
name: string
|
name: string;
|
||||||
}) => {
|
}) => {
|
||||||
try {
|
try {
|
||||||
await revokeToken({
|
await revokeToken({
|
||||||
identityId: parentIdentityId,
|
identityId: parentIdentityId,
|
||||||
tokenId,
|
tokenId
|
||||||
})
|
});
|
||||||
|
|
||||||
handlePopUpClose('revokeToken')
|
handlePopUpClose("revokeToken");
|
||||||
|
|
||||||
createNotification({
|
createNotification({
|
||||||
text: `Successfully revoked token ${name ?? ''}`,
|
text: `Successfully revoked token ${name ?? ""}`,
|
||||||
type: 'success',
|
type: "success"
|
||||||
})
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(err)
|
console.error(err);
|
||||||
const error = err as any
|
const error = err as any;
|
||||||
const text =
|
const text = error?.response?.data?.message ?? "Failed to delete identity";
|
||||||
error?.response?.data?.message ?? 'Failed to delete identity'
|
|
||||||
|
|
||||||
createNotification({
|
createNotification({
|
||||||
text,
|
text,
|
||||||
type: 'error',
|
type: "error"
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
const onDeleteClientSecretSubmit = async ({
|
const onDeleteClientSecretSubmit = async ({ clientSecretId }: { clientSecretId: string }) => {
|
||||||
clientSecretId,
|
|
||||||
}: {
|
|
||||||
clientSecretId: string
|
|
||||||
}) => {
|
|
||||||
try {
|
try {
|
||||||
if (
|
if (!data?.identity.id || selectedAuthMethod !== IdentityAuthMethod.UNIVERSAL_AUTH) return;
|
||||||
!data?.identity.id ||
|
|
||||||
selectedAuthMethod !== IdentityAuthMethod.UNIVERSAL_AUTH
|
|
||||||
)
|
|
||||||
return
|
|
||||||
|
|
||||||
await revokeClientSecret({
|
await revokeClientSecret({
|
||||||
identityId: data?.identity.id,
|
identityId: data?.identity.id,
|
||||||
clientSecretId,
|
clientSecretId
|
||||||
})
|
});
|
||||||
|
|
||||||
handlePopUpToggle('revokeClientSecret', false)
|
handlePopUpToggle("revokeClientSecret", false);
|
||||||
|
|
||||||
createNotification({
|
createNotification({
|
||||||
text: 'Successfully deleted client secret',
|
text: "Successfully deleted client secret",
|
||||||
type: 'success',
|
type: "success"
|
||||||
})
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(err)
|
console.error(err);
|
||||||
createNotification({
|
createNotification({
|
||||||
text: 'Failed to delete client secret',
|
text: "Failed to delete client secret",
|
||||||
type: 'error',
|
type: "error"
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="container mx-auto flex flex-col justify-between bg-bunker-800 text-white">
|
<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} />}
|
leftIcon={<FontAwesomeIcon icon={faChevronLeft} />}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
navigate({
|
navigate({
|
||||||
to: `/organization/${orgId}/members` as const,
|
to: "/organization/$organizationId/members",
|
||||||
search: {
|
params: {
|
||||||
selectedTab: TabSections.Identities,
|
organizationId: orgId
|
||||||
},
|
},
|
||||||
})
|
search: {
|
||||||
|
selectedTab: TabSections.Identities
|
||||||
|
}
|
||||||
|
});
|
||||||
}}
|
}}
|
||||||
className="mb-4"
|
className="mb-4"
|
||||||
>
|
>
|
||||||
Identities
|
Identities
|
||||||
</Button>
|
</Button>
|
||||||
<div className="mb-4 flex items-center justify-between">
|
<div className="mb-4 flex items-center justify-between">
|
||||||
<p className="text-3xl font-semibold text-white">
|
<p className="text-3xl font-semibold text-white">{data.identity.name}</p>
|
||||||
{data.identity.name}
|
|
||||||
</p>
|
|
||||||
<DropdownMenu>
|
<DropdownMenu>
|
||||||
<DropdownMenuTrigger asChild className="rounded-lg">
|
<DropdownMenuTrigger asChild className="rounded-lg">
|
||||||
<div className="hover:text-primary-400 data-[state=open]:text-primary-400">
|
<div className="hover:text-primary-400 data-[state=open]:text-primary-400">
|
||||||
@@ -220,16 +208,15 @@ export const IdentitySection = withPermission(
|
|||||||
{(isAllowed) => (
|
{(isAllowed) => (
|
||||||
<DropdownMenuItem
|
<DropdownMenuItem
|
||||||
className={twMerge(
|
className={twMerge(
|
||||||
!isAllowed &&
|
!isAllowed && "pointer-events-none cursor-not-allowed opacity-50"
|
||||||
'pointer-events-none cursor-not-allowed opacity-50',
|
|
||||||
)}
|
)}
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
handlePopUpOpen('identity', {
|
handlePopUpOpen("identity", {
|
||||||
identityId,
|
identityId,
|
||||||
name: data.identity.name,
|
name: data.identity.name,
|
||||||
role: data.role,
|
role: data.role,
|
||||||
customRole: data.customRole,
|
customRole: data.customRole
|
||||||
})
|
});
|
||||||
}}
|
}}
|
||||||
disabled={!isAllowed}
|
disabled={!isAllowed}
|
||||||
>
|
>
|
||||||
@@ -244,15 +231,14 @@ export const IdentitySection = withPermission(
|
|||||||
{(isAllowed) => (
|
{(isAllowed) => (
|
||||||
<DropdownMenuItem
|
<DropdownMenuItem
|
||||||
className={twMerge(
|
className={twMerge(
|
||||||
!isAllowed &&
|
!isAllowed && "pointer-events-none cursor-not-allowed opacity-50"
|
||||||
'pointer-events-none cursor-not-allowed opacity-50',
|
|
||||||
)}
|
)}
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
handlePopUpOpen('identityAuthMethod', {
|
handlePopUpOpen("identityAuthMethod", {
|
||||||
identityId,
|
identityId,
|
||||||
name: data.identity.name,
|
name: data.identity.name,
|
||||||
allAuthMethods: data.identity.authMethods,
|
allAuthMethods: data.identity.authMethods
|
||||||
})
|
});
|
||||||
}}
|
}}
|
||||||
disabled={!isAllowed}
|
disabled={!isAllowed}
|
||||||
>
|
>
|
||||||
@@ -268,14 +254,14 @@ export const IdentitySection = withPermission(
|
|||||||
<DropdownMenuItem
|
<DropdownMenuItem
|
||||||
className={twMerge(
|
className={twMerge(
|
||||||
isAllowed
|
isAllowed
|
||||||
? 'hover:!bg-red-500 hover:!text-white'
|
? "hover:!bg-red-500 hover:!text-white"
|
||||||
: 'pointer-events-none cursor-not-allowed opacity-50',
|
: "pointer-events-none cursor-not-allowed opacity-50"
|
||||||
)}
|
)}
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
handlePopUpOpen('deleteIdentity', {
|
handlePopUpOpen("deleteIdentity", {
|
||||||
identityId,
|
identityId,
|
||||||
name: data.identity.name,
|
name: data.identity.name
|
||||||
})
|
});
|
||||||
}}
|
}}
|
||||||
disabled={!isAllowed}
|
disabled={!isAllowed}
|
||||||
>
|
>
|
||||||
@@ -288,10 +274,7 @@ export const IdentitySection = withPermission(
|
|||||||
</div>
|
</div>
|
||||||
<div className="flex">
|
<div className="flex">
|
||||||
<div className="mr-4 w-96">
|
<div className="mr-4 w-96">
|
||||||
<IdentityDetailsSection
|
<IdentityDetailsSection identityId={identityId} handlePopUpOpen={handlePopUpOpen} />
|
||||||
identityId={identityId}
|
|
||||||
handlePopUpOpen={handlePopUpOpen}
|
|
||||||
/>
|
|
||||||
<IdentityAuthenticationSection
|
<IdentityAuthenticationSection
|
||||||
selectedAuthMethod={selectedAuthMethod}
|
selectedAuthMethod={selectedAuthMethod}
|
||||||
setSelectedAuthMethod={setSelectedAuthMethod}
|
setSelectedAuthMethod={setSelectedAuthMethod}
|
||||||
@@ -309,19 +292,13 @@ export const IdentitySection = withPermission(
|
|||||||
handlePopUpOpen={handlePopUpOpen}
|
handlePopUpOpen={handlePopUpOpen}
|
||||||
handlePopUpToggle={handlePopUpToggle}
|
handlePopUpToggle={handlePopUpToggle}
|
||||||
/>
|
/>
|
||||||
<IdentityTokenModal
|
<IdentityTokenModal popUp={popUp} handlePopUpToggle={handlePopUpToggle} />
|
||||||
popUp={popUp}
|
|
||||||
handlePopUpToggle={handlePopUpToggle}
|
|
||||||
/>
|
|
||||||
<IdentityTokenListModal
|
<IdentityTokenListModal
|
||||||
popUp={popUp}
|
popUp={popUp}
|
||||||
handlePopUpOpen={handlePopUpOpen}
|
handlePopUpOpen={handlePopUpOpen}
|
||||||
handlePopUpToggle={handlePopUpToggle}
|
handlePopUpToggle={handlePopUpToggle}
|
||||||
/>
|
/>
|
||||||
<IdentityClientSecretModal
|
<IdentityClientSecretModal popUp={popUp} handlePopUpToggle={handlePopUpToggle} />
|
||||||
popUp={popUp}
|
|
||||||
handlePopUpToggle={handlePopUpToggle}
|
|
||||||
/>
|
|
||||||
<IdentityUniversalAuthClientSecretModal
|
<IdentityUniversalAuthClientSecretModal
|
||||||
popUp={popUp}
|
popUp={popUp}
|
||||||
handlePopUpOpen={handlePopUpOpen}
|
handlePopUpOpen={handlePopUpOpen}
|
||||||
@@ -329,87 +306,82 @@ export const IdentitySection = withPermission(
|
|||||||
/>
|
/>
|
||||||
<UpgradePlanModal
|
<UpgradePlanModal
|
||||||
isOpen={popUp.upgradePlan.isOpen}
|
isOpen={popUp.upgradePlan.isOpen}
|
||||||
onOpenChange={(isOpen) => handlePopUpToggle('upgradePlan', isOpen)}
|
onOpenChange={(isOpen) => handlePopUpToggle("upgradePlan", isOpen)}
|
||||||
text={
|
text={(popUp.upgradePlan?.data as { description: string })?.description}
|
||||||
(popUp.upgradePlan?.data as { description: string })?.description
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
<DeleteActionModal
|
<DeleteActionModal
|
||||||
isOpen={popUp.deleteIdentity.isOpen}
|
isOpen={popUp.deleteIdentity.isOpen}
|
||||||
title={`Are you sure want to delete ${
|
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"
|
deleteKey="confirm"
|
||||||
onDeleteApproved={() =>
|
onDeleteApproved={() =>
|
||||||
onDeleteIdentitySubmit(
|
onDeleteIdentitySubmit(
|
||||||
(popUp?.deleteIdentity?.data as { identityId: string })
|
(popUp?.deleteIdentity?.data as { identityId: string })?.identityId
|
||||||
?.identityId,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<DeleteActionModal
|
<DeleteActionModal
|
||||||
isOpen={popUp.revokeToken.isOpen}
|
isOpen={popUp.revokeToken.isOpen}
|
||||||
title={`Are you sure want to revoke ${
|
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"
|
deleteKey="confirm"
|
||||||
onDeleteApproved={() => {
|
onDeleteApproved={() => {
|
||||||
const revokeTokenData = popUp?.revokeToken?.data as {
|
const revokeTokenData = popUp?.revokeToken?.data as {
|
||||||
identityId: string
|
identityId: string;
|
||||||
tokenId: string
|
tokenId: string;
|
||||||
name: string
|
name: string;
|
||||||
}
|
};
|
||||||
|
|
||||||
return onRevokeTokenSubmit(revokeTokenData)
|
return onRevokeTokenSubmit(revokeTokenData);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<DeleteActionModal
|
<DeleteActionModal
|
||||||
isOpen={popUp.revokeClientSecret.isOpen}
|
isOpen={popUp.revokeClientSecret.isOpen}
|
||||||
title={`Are you sure want to delete the client secret ${
|
title={`Are you sure want to delete the client secret ${
|
||||||
(popUp?.revokeClientSecret?.data as { clientSecretPrefix: string })
|
(popUp?.revokeClientSecret?.data as { clientSecretPrefix: string })
|
||||||
?.clientSecretPrefix || ''
|
?.clientSecretPrefix || ""
|
||||||
}************?`}
|
}************?`}
|
||||||
onChange={(isOpen) => handlePopUpToggle('revokeClientSecret', isOpen)}
|
onChange={(isOpen) => handlePopUpToggle("revokeClientSecret", isOpen)}
|
||||||
deleteKey="confirm"
|
deleteKey="confirm"
|
||||||
onDeleteApproved={() => {
|
onDeleteApproved={() => {
|
||||||
const deleteClientSecretData = popUp?.revokeClientSecret?.data as {
|
const deleteClientSecretData = popUp?.revokeClientSecret?.data as {
|
||||||
clientSecretId: string
|
clientSecretId: string;
|
||||||
clientSecretPrefix: string
|
clientSecretPrefix: string;
|
||||||
}
|
};
|
||||||
|
|
||||||
return onDeleteClientSecretSubmit({
|
return onDeleteClientSecretSubmit({
|
||||||
clientSecretId: deleteClientSecretData.clientSecretId,
|
clientSecretId: deleteClientSecretData.clientSecretId
|
||||||
})
|
});
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)
|
);
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
action: OrgPermissionActions.Read,
|
action: OrgPermissionActions.Read,
|
||||||
subject: OrgPermissionSubjects.Identity,
|
subject: OrgPermissionSubjects.Identity
|
||||||
},
|
}
|
||||||
)
|
);
|
||||||
|
|
||||||
const IdentityDetailPage = () => {
|
const IdentityDetailPage = () => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation();
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Helmet>
|
<Helmet>
|
||||||
<title>
|
<title>{t("common.head-title", { title: t("settings.org.title") })}</title>
|
||||||
{t('common.head-title', { title: t('settings.org.title') })}
|
|
||||||
</title>
|
|
||||||
<link rel="icon" href="/infisical.ico" />
|
<link rel="icon" href="/infisical.ico" />
|
||||||
</Helmet>
|
</Helmet>
|
||||||
<IdentitySection />
|
<IdentitySection />
|
||||||
</>
|
</>
|
||||||
)
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
export const Route = createFileRoute(
|
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
|
||||||
})
|
});
|
||||||
|
|||||||
+7
-7
@@ -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(
|
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
|
||||||
})
|
});
|
||||||
|
|||||||
-1
@@ -15,4 +15,3 @@ export const OrgGroupsTab = () => {
|
|||||||
</motion.div>
|
</motion.div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -8,6 +8,7 @@ import {
|
|||||||
faUsers
|
faUsers
|
||||||
} from "@fortawesome/free-solid-svg-icons";
|
} from "@fortawesome/free-solid-svg-icons";
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
|
import { useNavigate } from "@tanstack/react-router";
|
||||||
import { twMerge } from "tailwind-merge";
|
import { twMerge } from "tailwind-merge";
|
||||||
|
|
||||||
import { createNotification } from "@app/components/notifications";
|
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 { useGetOrganizationGroups, useGetOrgRoles, useUpdateGroup } from "@app/hooks/api";
|
||||||
import { OrderByDirection } from "@app/hooks/api/generic/types";
|
import { OrderByDirection } from "@app/hooks/api/generic/types";
|
||||||
import { UsePopUpState } from "@app/hooks/usePopUp";
|
import { UsePopUpState } from "@app/hooks/usePopUp";
|
||||||
import { useNavigate } from "@tanstack/react-router";
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
handlePopUpOpen: (
|
handlePopUpOpen: (
|
||||||
|
|||||||
+8
-2
@@ -6,6 +6,7 @@ import {
|
|||||||
faServer
|
faServer
|
||||||
} from "@fortawesome/free-solid-svg-icons";
|
} from "@fortawesome/free-solid-svg-icons";
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
|
import { useNavigate } from "@tanstack/react-router";
|
||||||
import { twMerge } from "tailwind-merge";
|
import { twMerge } from "tailwind-merge";
|
||||||
|
|
||||||
import { createNotification } from "@app/components/notifications";
|
import { createNotification } from "@app/components/notifications";
|
||||||
@@ -37,7 +38,6 @@ import { useGetIdentityMembershipOrgs, useGetOrgRoles, useUpdateIdentity } from
|
|||||||
import { OrderByDirection } from "@app/hooks/api/generic/types";
|
import { OrderByDirection } from "@app/hooks/api/generic/types";
|
||||||
import { OrgIdentityOrderBy } from "@app/hooks/api/organization/types";
|
import { OrgIdentityOrderBy } from "@app/hooks/api/organization/types";
|
||||||
import { UsePopUpState } from "@app/hooks/usePopUp";
|
import { UsePopUpState } from "@app/hooks/usePopUp";
|
||||||
import { useNavigate } from "@tanstack/react-router";
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
handlePopUpOpen: (
|
handlePopUpOpen: (
|
||||||
@@ -254,7 +254,13 @@ export const IdentityTable = ({ handlePopUpOpen }: Props) => {
|
|||||||
)}
|
)}
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
navigate.push(`/org/${organizationId}/identities/${id}`);
|
navigate({
|
||||||
|
to: "/organization/$organizationId/identities/$identityId",
|
||||||
|
params: {
|
||||||
|
organizationId,
|
||||||
|
identityId: id
|
||||||
|
}
|
||||||
|
});
|
||||||
}}
|
}}
|
||||||
disabled={!isAllowed}
|
disabled={!isAllowed}
|
||||||
>
|
>
|
||||||
|
|||||||
+37
-48
@@ -1,54 +1,40 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||||
import { useEffect, useState } from 'react'
|
import { useEffect, useState } from "react";
|
||||||
import { Helmet } from 'react-helmet'
|
import { Helmet } from "react-helmet";
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from "react-i18next";
|
||||||
import { createFileRoute, useNavigate, useSearch } from '@tanstack/react-router'
|
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 { Tab, TabList, TabPanel, Tabs } from "@app/components/v2";
|
||||||
import { OrgPermissionActions, OrgPermissionSubjects } from '@app/context'
|
import { OrgPermissionActions, OrgPermissionSubjects } from "@app/context";
|
||||||
import { withPermission } from '@app/hoc'
|
import { withPermission } from "@app/hoc";
|
||||||
import { TabSections } from '@app/types/org'
|
import { TabSections } from "@app/types/org";
|
||||||
|
|
||||||
import {
|
import { OrgGroupsTab, OrgIdentityTab, OrgMembersTab, OrgRoleTabSection } from "./-components";
|
||||||
OrgGroupsTab,
|
|
||||||
OrgIdentityTab,
|
|
||||||
OrgMembersTab,
|
|
||||||
OrgRoleTabSection,
|
|
||||||
} from './-components'
|
|
||||||
|
|
||||||
export const MembersSection = withPermission(
|
export const MembersSection = withPermission(
|
||||||
() => {
|
() => {
|
||||||
const navigate = useNavigate({
|
const navigate = useNavigate({
|
||||||
from: '/_authenticate/_org_details/_org-layout/organization/$organizationId/members',
|
from: "/organization/$organizationId/members"
|
||||||
})
|
});
|
||||||
const search = useSearch({
|
const selectedTab = useSearch({
|
||||||
from: '/_authenticate/_org_details/_org-layout/organization/$organizationId/members',
|
from: "/_authenticate/_org_details/_org-layout/organization/$organizationId/members/",
|
||||||
})
|
select: (el) => el.selectedTab,
|
||||||
const selectedTab = search.selectedTab as TabSections
|
structuralSharing: true
|
||||||
const [activeTab, setActiveTab] = useState<TabSections>(TabSections.Member)
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (
|
|
||||||
selectedTab &&
|
|
||||||
Object.values(TabSections).includes(selectedTab as TabSections)
|
|
||||||
) {
|
|
||||||
setActiveTab(selectedTab)
|
|
||||||
}
|
|
||||||
}, [selectedTab])
|
|
||||||
|
|
||||||
const updateSelectedTab = (tab: string) => {
|
const updateSelectedTab = (tab: string) => {
|
||||||
navigate({
|
navigate({
|
||||||
search: { ...search, selectedTab: tab },
|
search: { selectedTab: tab }
|
||||||
})
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="container mx-auto flex flex-col justify-between bg-bunker-800 text-white">
|
<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">
|
<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">
|
<p className="mb-4 mr-4 text-3xl font-semibold text-white">Organization Access Control</p>
|
||||||
Organization Access Control
|
<Tabs value={selectedTab} onValueChange={updateSelectedTab}>
|
||||||
</p>
|
|
||||||
<Tabs value={activeTab} onValueChange={updateSelectedTab}>
|
|
||||||
<TabList>
|
<TabList>
|
||||||
<Tab value={TabSections.Member}>Users</Tab>
|
<Tab value={TabSections.Member}>Users</Tab>
|
||||||
<Tab value={TabSections.Groups}>Groups</Tab>
|
<Tab value={TabSections.Groups}>Groups</Tab>
|
||||||
@@ -74,29 +60,32 @@ export const MembersSection = withPermission(
|
|||||||
</Tabs>
|
</Tabs>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
);
|
||||||
},
|
},
|
||||||
{ action: OrgPermissionActions.Read, subject: OrgPermissionSubjects.Member },
|
{ action: OrgPermissionActions.Read, subject: OrgPermissionSubjects.Member }
|
||||||
)
|
);
|
||||||
|
|
||||||
const MembersPage = () => {
|
const MembersPage = () => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Helmet>
|
<Helmet>
|
||||||
<title>
|
<title>{t("common.head-title", { title: t("settings.org.title") })}</title>
|
||||||
{t('common.head-title', { title: t('settings.org.title') })}
|
|
||||||
</title>
|
|
||||||
<link rel="icon" href="/infisical.ico" />
|
<link rel="icon" href="/infisical.ico" />
|
||||||
</Helmet>
|
</Helmet>
|
||||||
<MembersSection />
|
<MembersSection />
|
||||||
</>
|
</>
|
||||||
)
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
|
const MembersPageQuerySchema = z.object({
|
||||||
|
selectedTab: z.string().catch(TabSections.Member)
|
||||||
|
});
|
||||||
|
|
||||||
export const Route = createFileRoute(
|
export const Route = createFileRoute(
|
||||||
'/_authenticate/_org_details/_org-layout/organization/$organizationId/members/',
|
"/_authenticate/_org_details/_org-layout/organization/$organizationId/members/"
|
||||||
)({
|
)({
|
||||||
component: MembersPage,
|
component: MembersPage,
|
||||||
})
|
validateSearch: zodValidator(MembersPageQuerySchema)
|
||||||
|
});
|
||||||
|
|||||||
+2
@@ -7,6 +7,8 @@ import { OrgPermissionActions, OrgPermissionSubjects, useSubscription } from "@a
|
|||||||
import { withPermission } from "@app/hoc";
|
import { withPermission } from "@app/hoc";
|
||||||
import { OrgUser } from "@app/hooks/api/types";
|
import { OrgUser } from "@app/hooks/api/types";
|
||||||
|
|
||||||
|
import { LogsSection } from "../../../../audit-logs/-components";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
orgMembership: OrgUser;
|
orgMembership: OrgUser;
|
||||||
};
|
};
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,7 @@
|
|||||||
import { useMemo } from "react";
|
import { useMemo } from "react";
|
||||||
import { faTrash } from "@fortawesome/free-solid-svg-icons";
|
import { faTrash } from "@fortawesome/free-solid-svg-icons";
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
|
import { useNavigate } from "@tanstack/react-router";
|
||||||
|
|
||||||
import { createNotification } from "@app/components/notifications";
|
import { createNotification } from "@app/components/notifications";
|
||||||
import { IconButton, Tag, Td, Tooltip, Tr } from "@app/components/v2";
|
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 { ProjectMembershipRole } from "@app/hooks/api/roles/types";
|
||||||
import { TWorkspaceUser } from "@app/hooks/api/types";
|
import { TWorkspaceUser } from "@app/hooks/api/types";
|
||||||
import { UsePopUpState } from "@app/hooks/usePopUp";
|
import { UsePopUpState } from "@app/hooks/usePopUp";
|
||||||
import { useNavigate } from "@tanstack/react-router";
|
|
||||||
import { TabSections } from "@app/types/org";
|
import { TabSections } from "@app/types/org";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
|
|||||||
+10
-9
@@ -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(
|
export const Route = createFileRoute(
|
||||||
'/_authenticate/_org_details/_org-layout/organization/$organizationId/overview/',
|
"/_authenticate/_org_details/_org-layout/organization/$organizationId/overview/"
|
||||||
)({
|
)({
|
||||||
beforeLoad: ({ context }) => {
|
beforeLoad: ({ context }) => {
|
||||||
throw redirect({
|
throw redirect({
|
||||||
to: `/organization/$organizationId/${ProjectType.SecretManager}` as const,
|
to: `/organization/$organizationId/${ProjectType.SecretManager}/overview` as const,
|
||||||
params: {
|
params: {
|
||||||
organizationId: context.organizationId,
|
organizationId: context.organizationId
|
||||||
},
|
}
|
||||||
})
|
});
|
||||||
},
|
}
|
||||||
})
|
});
|
||||||
|
|||||||
+2
-2
@@ -1,6 +1,7 @@
|
|||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
import { Controller, useForm } from "react-hook-form";
|
import { Controller, useForm } from "react-hook-form";
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
|
import { useNavigate } from "@tanstack/react-router";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import { createNotification } from "@app/components/notifications";
|
import { createNotification } from "@app/components/notifications";
|
||||||
@@ -9,7 +10,6 @@ import { useOrganization } from "@app/context";
|
|||||||
import { useCreateOrgRole, useGetOrgRole, useUpdateOrgRole } from "@app/hooks/api";
|
import { useCreateOrgRole, useGetOrgRole, useUpdateOrgRole } from "@app/hooks/api";
|
||||||
import { UsePopUpState } from "@app/hooks/usePopUp";
|
import { UsePopUpState } from "@app/hooks/usePopUp";
|
||||||
import { slugSchema } from "@app/lib/schemas";
|
import { slugSchema } from "@app/lib/schemas";
|
||||||
import { useNavigate } from "@tanstack/react-router";
|
|
||||||
|
|
||||||
const schema = z
|
const schema = z
|
||||||
.object({
|
.object({
|
||||||
@@ -104,7 +104,7 @@ export const RoleModal = ({ popUp, handlePopUpToggle }: Props) => {
|
|||||||
|
|
||||||
handlePopUpToggle("role", false);
|
handlePopUpToggle("role", false);
|
||||||
navigate({
|
navigate({
|
||||||
to: "/organization/$organizationId/roles/roleId",
|
to: "/organization/$organizationId/roles/$roleId",
|
||||||
params: {
|
params: {
|
||||||
organizationId: orgId,
|
organizationId: orgId,
|
||||||
roleId: newRole.id
|
roleId: newRole.id
|
||||||
|
|||||||
+73
-91
@@ -1,12 +1,12 @@
|
|||||||
import { Helmet } from 'react-helmet'
|
import { Helmet } from "react-helmet";
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from "react-i18next";
|
||||||
import { faChevronLeft, faEllipsis } from '@fortawesome/free-solid-svg-icons'
|
import { faChevronLeft, faEllipsis } from "@fortawesome/free-solid-svg-icons";
|
||||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
import { createFileRoute, useNavigate, useParams } from '@tanstack/react-router'
|
import { createFileRoute, useNavigate, useParams } from "@tanstack/react-router";
|
||||||
import { twMerge } from 'tailwind-merge'
|
import { twMerge } from "tailwind-merge";
|
||||||
|
|
||||||
import { createNotification } from '@app/components/notifications'
|
import { createNotification } from "@app/components/notifications";
|
||||||
import { OrgPermissionCan } from '@app/components/permissions'
|
import { OrgPermissionCan } from "@app/components/permissions";
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
DeleteActionModal,
|
DeleteActionModal,
|
||||||
@@ -14,79 +14,70 @@ import {
|
|||||||
DropdownMenuContent,
|
DropdownMenuContent,
|
||||||
DropdownMenuItem,
|
DropdownMenuItem,
|
||||||
DropdownMenuTrigger,
|
DropdownMenuTrigger,
|
||||||
Tooltip,
|
Tooltip
|
||||||
} from '@app/components/v2'
|
} from "@app/components/v2";
|
||||||
import {
|
import { OrgPermissionActions, OrgPermissionSubjects, useOrganization } from "@app/context";
|
||||||
OrgPermissionActions,
|
import { withPermission } from "@app/hoc";
|
||||||
OrgPermissionSubjects,
|
import { useDeleteOrgRole, useGetOrgRole } from "@app/hooks/api";
|
||||||
useOrganization,
|
import { usePopUp } from "@app/hooks/usePopUp";
|
||||||
} from '@app/context'
|
import { TabSections } from "@app/types/org";
|
||||||
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 {
|
import { RoleDetailsSection, RoleModal, RolePermissionsSection } from "./-components";
|
||||||
RoleDetailsSection,
|
|
||||||
RoleModal,
|
|
||||||
RolePermissionsSection,
|
|
||||||
} from './-components'
|
|
||||||
|
|
||||||
export const RolePage = withPermission(
|
export const RolePage = withPermission(
|
||||||
() => {
|
() => {
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate();
|
||||||
const params = useParams({
|
const params = useParams({
|
||||||
from: '/_authenticate/_org_details/_org-layout/organization/$organizationId/roles/$roleId',
|
from: "/_authenticate/_org_details/_org-layout/organization/$organizationId/roles/$roleId/"
|
||||||
})
|
});
|
||||||
const roleId = params.roleId as string
|
const roleId = params.roleId as string;
|
||||||
const { currentOrg } = useOrganization()
|
const { currentOrg } = useOrganization();
|
||||||
const orgId = currentOrg?.id || ''
|
const orgId = currentOrg?.id || "";
|
||||||
const { data } = useGetOrgRole(orgId, roleId)
|
const { data } = useGetOrgRole(orgId, roleId);
|
||||||
const { mutateAsync: deleteOrgRole } = useDeleteOrgRole()
|
const { mutateAsync: deleteOrgRole } = useDeleteOrgRole();
|
||||||
|
|
||||||
const { popUp, handlePopUpOpen, handlePopUpClose, handlePopUpToggle } =
|
const { popUp, handlePopUpOpen, handlePopUpClose, handlePopUpToggle } = usePopUp([
|
||||||
usePopUp(['role', 'deleteOrgRole'] as const)
|
"role",
|
||||||
|
"deleteOrgRole"
|
||||||
|
] as const);
|
||||||
|
|
||||||
const onDeleteOrgRoleSubmit = async () => {
|
const onDeleteOrgRoleSubmit = async () => {
|
||||||
try {
|
try {
|
||||||
if (!orgId || !roleId) return
|
if (!orgId || !roleId) return;
|
||||||
|
|
||||||
await deleteOrgRole({
|
await deleteOrgRole({
|
||||||
orgId,
|
orgId,
|
||||||
id: roleId,
|
id: roleId
|
||||||
})
|
});
|
||||||
|
|
||||||
createNotification({
|
createNotification({
|
||||||
text: 'Successfully deleted organization role',
|
text: "Successfully deleted organization role",
|
||||||
type: 'success',
|
type: "success"
|
||||||
})
|
});
|
||||||
|
|
||||||
handlePopUpClose('deleteOrgRole')
|
handlePopUpClose("deleteOrgRole");
|
||||||
navigate({
|
navigate({
|
||||||
to: '/organization/$organizationId/members' as const,
|
to: "/organization/$organizationId/members" as const,
|
||||||
params: {
|
params: {
|
||||||
organizationId: currentOrg.id,
|
organizationId: currentOrg.id
|
||||||
},
|
},
|
||||||
search: {
|
search: {
|
||||||
selectedTab: TabSections.Roles,
|
selectedTab: TabSections.Roles
|
||||||
},
|
}
|
||||||
})
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(err)
|
console.error(err);
|
||||||
const error = err as any
|
const error = err as any;
|
||||||
const text =
|
const text = error?.response?.data?.message ?? "Failed to delete organization role";
|
||||||
error?.response?.data?.message ?? 'Failed to delete organization role'
|
|
||||||
|
|
||||||
createNotification({
|
createNotification({
|
||||||
text,
|
text,
|
||||||
type: 'error',
|
type: "error"
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
const isCustomRole = !['admin', 'member', 'no-access'].includes(
|
const isCustomRole = !["admin", "member", "no-access"].includes(data?.slug ?? "");
|
||||||
data?.slug ?? '',
|
|
||||||
)
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="container mx-auto flex flex-col justify-between bg-bunker-800 text-white">
|
<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} />}
|
leftIcon={<FontAwesomeIcon icon={faChevronLeft} />}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
navigate({
|
navigate({
|
||||||
to: '/organization/$organizationId/members' as const,
|
to: "/organization/$organizationId/members" as const,
|
||||||
params: {
|
params: {
|
||||||
organizationId: currentOrg.id,
|
organizationId: currentOrg.id
|
||||||
},
|
},
|
||||||
search: {
|
search: {
|
||||||
selectedTab: TabSections.Roles,
|
selectedTab: TabSections.Roles
|
||||||
},
|
}
|
||||||
})
|
});
|
||||||
}}
|
}}
|
||||||
className="mb-4"
|
className="mb-4"
|
||||||
>
|
>
|
||||||
@@ -123,20 +114,16 @@ export const RolePage = withPermission(
|
|||||||
</div>
|
</div>
|
||||||
</DropdownMenuTrigger>
|
</DropdownMenuTrigger>
|
||||||
<DropdownMenuContent align="start" className="p-1">
|
<DropdownMenuContent align="start" className="p-1">
|
||||||
<OrgPermissionCan
|
<OrgPermissionCan I={OrgPermissionActions.Edit} a={OrgPermissionSubjects.Role}>
|
||||||
I={OrgPermissionActions.Edit}
|
|
||||||
a={OrgPermissionSubjects.Role}
|
|
||||||
>
|
|
||||||
{(isAllowed) => (
|
{(isAllowed) => (
|
||||||
<DropdownMenuItem
|
<DropdownMenuItem
|
||||||
className={twMerge(
|
className={twMerge(
|
||||||
!isAllowed &&
|
!isAllowed && "pointer-events-none cursor-not-allowed opacity-50"
|
||||||
'pointer-events-none cursor-not-allowed opacity-50',
|
|
||||||
)}
|
)}
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
handlePopUpOpen('role', {
|
handlePopUpOpen("role", {
|
||||||
roleId,
|
roleId
|
||||||
})
|
});
|
||||||
}}
|
}}
|
||||||
disabled={!isAllowed}
|
disabled={!isAllowed}
|
||||||
>
|
>
|
||||||
@@ -152,11 +139,11 @@ export const RolePage = withPermission(
|
|||||||
<DropdownMenuItem
|
<DropdownMenuItem
|
||||||
className={twMerge(
|
className={twMerge(
|
||||||
isAllowed
|
isAllowed
|
||||||
? 'hover:!bg-red-500 hover:!text-white'
|
? "hover:!bg-red-500 hover:!text-white"
|
||||||
: 'pointer-events-none cursor-not-allowed opacity-50',
|
: "pointer-events-none cursor-not-allowed opacity-50"
|
||||||
)}
|
)}
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
handlePopUpOpen('deleteOrgRole')
|
handlePopUpOpen("deleteOrgRole");
|
||||||
}}
|
}}
|
||||||
disabled={!isAllowed}
|
disabled={!isAllowed}
|
||||||
>
|
>
|
||||||
@@ -170,10 +157,7 @@ export const RolePage = withPermission(
|
|||||||
</div>
|
</div>
|
||||||
<div className="flex">
|
<div className="flex">
|
||||||
<div className="mr-4 w-96">
|
<div className="mr-4 w-96">
|
||||||
<RoleDetailsSection
|
<RoleDetailsSection roleId={roleId} handlePopUpOpen={handlePopUpOpen} />
|
||||||
roleId={roleId}
|
|
||||||
handlePopUpOpen={handlePopUpOpen}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<RolePermissionsSection roleId={roleId} />
|
<RolePermissionsSection roleId={roleId} />
|
||||||
</div>
|
</div>
|
||||||
@@ -182,34 +166,32 @@ export const RolePage = withPermission(
|
|||||||
<RoleModal popUp={popUp} handlePopUpToggle={handlePopUpToggle} />
|
<RoleModal popUp={popUp} handlePopUpToggle={handlePopUpToggle} />
|
||||||
<DeleteActionModal
|
<DeleteActionModal
|
||||||
isOpen={popUp.deleteOrgRole.isOpen}
|
isOpen={popUp.deleteOrgRole.isOpen}
|
||||||
title={`Are you sure want to delete the organization role ${data?.name ?? ''}?`}
|
title={`Are you sure want to delete the organization role ${data?.name ?? ""}?`}
|
||||||
onChange={(isOpen) => handlePopUpToggle('deleteOrgRole', isOpen)}
|
onChange={(isOpen) => handlePopUpToggle("deleteOrgRole", isOpen)}
|
||||||
deleteKey="confirm"
|
deleteKey="confirm"
|
||||||
onDeleteApproved={() => onDeleteOrgRoleSubmit()}
|
onDeleteApproved={() => onDeleteOrgRoleSubmit()}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)
|
);
|
||||||
},
|
},
|
||||||
{ action: OrgPermissionActions.Read, subject: OrgPermissionSubjects.Role },
|
{ action: OrgPermissionActions.Read, subject: OrgPermissionSubjects.Role }
|
||||||
)
|
);
|
||||||
|
|
||||||
const OrgRoleRoute = () => {
|
const OrgRoleRoute = () => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation();
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Helmet>
|
<Helmet>
|
||||||
<title>
|
<title>{t("common.head-title", { title: t("settings.org.title") })}</title>
|
||||||
{t('common.head-title', { title: t('settings.org.title') })}
|
|
||||||
</title>
|
|
||||||
<link rel="icon" href="/infisical.ico" />
|
<link rel="icon" href="/infisical.ico" />
|
||||||
</Helmet>
|
</Helmet>
|
||||||
<RolePage />
|
<RolePage />
|
||||||
</>
|
</>
|
||||||
)
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
export const Route = createFileRoute(
|
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
|
||||||
})
|
});
|
||||||
|
|||||||
+146
-182
@@ -1,9 +1,9 @@
|
|||||||
// REFACTOR(akhilmhdh): This file needs to be split into multiple components too complex
|
// REFACTOR(akhilmhdh): This file needs to be split into multiple components too complex
|
||||||
|
|
||||||
import { ReactNode, useMemo, useState } from 'react'
|
import { ReactNode, useMemo, useState } from "react";
|
||||||
import { Helmet } from 'react-helmet'
|
import { Helmet } from "react-helmet";
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from "react-i18next";
|
||||||
import { faFolderOpen, faStar } from '@fortawesome/free-regular-svg-icons'
|
import { faFolderOpen, faStar } from "@fortawesome/free-regular-svg-icons";
|
||||||
import {
|
import {
|
||||||
faArrowDownAZ,
|
faArrowDownAZ,
|
||||||
faArrowRight,
|
faArrowRight,
|
||||||
@@ -14,14 +14,14 @@ import {
|
|||||||
faMagnifyingGlass,
|
faMagnifyingGlass,
|
||||||
faPlus,
|
faPlus,
|
||||||
faSearch,
|
faSearch,
|
||||||
faStar as faSolidStar,
|
faStar as faSolidStar
|
||||||
} from '@fortawesome/free-solid-svg-icons'
|
} from "@fortawesome/free-solid-svg-icons";
|
||||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
import { createFileRoute, useNavigate } from '@tanstack/react-router'
|
import { createFileRoute, useNavigate } from "@tanstack/react-router";
|
||||||
|
|
||||||
import { createNotification } from '@app/components/notifications'
|
import { createNotification } from "@app/components/notifications";
|
||||||
import { OrgPermissionCan } from '@app/components/permissions'
|
import { OrgPermissionCan } from "@app/components/permissions";
|
||||||
import { NewProjectModal } from '@app/components/projects'
|
import { NewProjectModal } from "@app/components/projects";
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
IconButton,
|
IconButton,
|
||||||
@@ -29,89 +29,86 @@ import {
|
|||||||
Pagination,
|
Pagination,
|
||||||
Skeleton,
|
Skeleton,
|
||||||
Tooltip,
|
Tooltip,
|
||||||
UpgradePlanModal,
|
UpgradePlanModal
|
||||||
} from '@app/components/v2'
|
} from "@app/components/v2";
|
||||||
import {
|
import {
|
||||||
OrgPermissionActions,
|
OrgPermissionActions,
|
||||||
OrgPermissionSubjects,
|
OrgPermissionSubjects,
|
||||||
useOrganization,
|
useOrganization,
|
||||||
useSubscription,
|
useSubscription
|
||||||
} from '@app/context'
|
} from "@app/context";
|
||||||
import { getProjectHomePage } from '@app/helpers/project'
|
import { getProjectHomePage } from "@app/helpers/project";
|
||||||
import { usePagination, useResetPageHelper } from '@app/hooks'
|
import { usePagination, useResetPageHelper } from "@app/hooks";
|
||||||
import { useGetUserWorkspaces } from '@app/hooks/api'
|
import { useGetUserWorkspaces } from "@app/hooks/api";
|
||||||
import { OrderByDirection } from '@app/hooks/api/generic/types'
|
import { OrderByDirection } from "@app/hooks/api/generic/types";
|
||||||
// import { fetchUserWsKey } from "@app/hooks/api/keys/queries";
|
// import { fetchUserWsKey } from "@app/hooks/api/keys/queries";
|
||||||
import { useFetchServerStatus } from '@app/hooks/api/serverDetails'
|
import { useFetchServerStatus } from "@app/hooks/api/serverDetails";
|
||||||
import { Workspace } from '@app/hooks/api/types'
|
import { Workspace } from "@app/hooks/api/types";
|
||||||
import { useUpdateUserProjectFavorites } from '@app/hooks/api/users/mutation'
|
import { useUpdateUserProjectFavorites } from "@app/hooks/api/users/mutation";
|
||||||
import { useGetUserProjectFavorites } from '@app/hooks/api/users/queries'
|
import { useGetUserProjectFavorites } from "@app/hooks/api/users/queries";
|
||||||
import { ProjectType } from '@app/hooks/api/workspace/types'
|
import { ProjectType } from "@app/hooks/api/workspace/types";
|
||||||
import { usePopUp } from '@app/hooks/usePopUp'
|
import { usePopUp } from "@app/hooks/usePopUp";
|
||||||
|
|
||||||
enum ProjectsViewMode {
|
enum ProjectsViewMode {
|
||||||
GRID = 'grid',
|
GRID = "grid",
|
||||||
LIST = 'list',
|
LIST = "list"
|
||||||
}
|
}
|
||||||
|
|
||||||
enum ProjectOrderBy {
|
enum ProjectOrderBy {
|
||||||
Name = 'name',
|
Name = "name"
|
||||||
}
|
}
|
||||||
|
|
||||||
const formatTitle = (type: ProjectType) => {
|
const formatTitle = (type: ProjectType) => {
|
||||||
if (type === ProjectType.SecretManager) return 'Secret Management'
|
if (type === ProjectType.SecretManager) return "Secret Management";
|
||||||
if (type === ProjectType.CertificateManager) return 'Cert Management'
|
if (type === ProjectType.CertificateManager) return "Cert Management";
|
||||||
return 'Key Management'
|
return "Key Management";
|
||||||
}
|
};
|
||||||
|
|
||||||
const formatDescription = (type: ProjectType) => {
|
const formatDescription = (type: ProjectType) => {
|
||||||
if (type === ProjectType.SecretManager)
|
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)
|
if (type === ProjectType.CertificateManager)
|
||||||
return 'Manage your PKI infrastructure and issue digital certificates for services, applications, and devices.'
|
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 "Centralize the management of keys for cryptographic operations, such as encryption and decryption.";
|
||||||
}
|
};
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
type: ProjectType
|
type: ProjectType;
|
||||||
}
|
};
|
||||||
|
|
||||||
// #TODO: Update all the workspaceIds
|
// #TODO: Update all the workspaceIds
|
||||||
export const ProductOverview = ({ type }: Props) => {
|
export const ProductOverview = ({ type }: Props) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate();
|
||||||
|
|
||||||
const { data: workspaces, isLoading: isWorkspaceLoading } =
|
const { data: workspaces, isLoading: isWorkspaceLoading } = useGetUserWorkspaces({ type });
|
||||||
useGetUserWorkspaces({ type })
|
const { currentOrg } = useOrganization();
|
||||||
const { currentOrg } = useOrganization()
|
const orgWorkspaces = workspaces || [];
|
||||||
const orgWorkspaces = workspaces || []
|
|
||||||
const { data: projectFavorites, isLoading: isProjectFavoritesLoading } =
|
const { data: projectFavorites, isLoading: isProjectFavoritesLoading } =
|
||||||
useGetUserProjectFavorites(currentOrg?.id)
|
useGetUserProjectFavorites(currentOrg?.id);
|
||||||
const { mutateAsync: updateUserProjectFavorites } =
|
const { mutateAsync: updateUserProjectFavorites } = useUpdateUserProjectFavorites();
|
||||||
useUpdateUserProjectFavorites()
|
|
||||||
|
|
||||||
const isProjectViewLoading = isWorkspaceLoading || isProjectFavoritesLoading
|
const isProjectViewLoading = isWorkspaceLoading || isProjectFavoritesLoading;
|
||||||
|
|
||||||
const { popUp, handlePopUpOpen, handlePopUpToggle } = usePopUp([
|
const { popUp, handlePopUpOpen, handlePopUpToggle } = usePopUp([
|
||||||
'addNewWs',
|
"addNewWs",
|
||||||
'upgradePlan',
|
"upgradePlan"
|
||||||
] as const)
|
] as const);
|
||||||
|
|
||||||
const [searchFilter, setSearchFilter] = useState('')
|
const [searchFilter, setSearchFilter] = useState("");
|
||||||
const { data: serverDetails } = useFetchServerStatus()
|
const { data: serverDetails } = useFetchServerStatus();
|
||||||
const [projectsViewMode, setProjectsViewMode] = useState<ProjectsViewMode>(
|
const [projectsViewMode, setProjectsViewMode] = useState<ProjectsViewMode>(
|
||||||
(localStorage.getItem('projectsViewMode') as ProjectsViewMode) ||
|
(localStorage.getItem("projectsViewMode") as ProjectsViewMode) || ProjectsViewMode.GRID
|
||||||
ProjectsViewMode.GRID,
|
);
|
||||||
)
|
|
||||||
|
|
||||||
const { subscription } = useSubscription()
|
const { subscription } = useSubscription();
|
||||||
|
|
||||||
const isAddingProjectsAllowed = subscription?.workspaceLimit
|
const isAddingProjectsAllowed = subscription?.workspaceLimit
|
||||||
? subscription.workspacesUsed < subscription.workspaceLimit
|
? subscription.workspacesUsed < subscription.workspaceLimit
|
||||||
: true
|
: true;
|
||||||
|
|
||||||
const isWorkspaceEmpty = !isProjectViewLoading && orgWorkspaces?.length === 0
|
const isWorkspaceEmpty = !isProjectViewLoading && orgWorkspaces?.length === 0;
|
||||||
|
|
||||||
const {
|
const {
|
||||||
setPage,
|
setPage,
|
||||||
@@ -121,76 +118,72 @@ export const ProductOverview = ({ type }: Props) => {
|
|||||||
offset,
|
offset,
|
||||||
limit,
|
limit,
|
||||||
toggleOrderDirection,
|
toggleOrderDirection,
|
||||||
orderDirection,
|
orderDirection
|
||||||
} = usePagination(ProjectOrderBy.Name, { initPerPage: 24 })
|
} = usePagination(ProjectOrderBy.Name, { initPerPage: 24 });
|
||||||
|
|
||||||
const filteredWorkspaces = useMemo(
|
const filteredWorkspaces = useMemo(
|
||||||
() =>
|
() =>
|
||||||
orgWorkspaces
|
orgWorkspaces
|
||||||
.filter((ws) =>
|
.filter((ws) => ws?.name?.toLowerCase().includes(searchFilter.toLowerCase()))
|
||||||
ws?.name?.toLowerCase().includes(searchFilter.toLowerCase()),
|
|
||||||
)
|
|
||||||
.sort((a, b) =>
|
.sort((a, b) =>
|
||||||
orderDirection === OrderByDirection.ASC
|
orderDirection === OrderByDirection.ASC
|
||||||
? a.name.toLowerCase().localeCompare(b.name.toLowerCase())
|
? 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({
|
useResetPageHelper({
|
||||||
setPage,
|
setPage,
|
||||||
offset,
|
offset,
|
||||||
totalCount: filteredWorkspaces.length,
|
totalCount: filteredWorkspaces.length
|
||||||
})
|
});
|
||||||
|
|
||||||
const { workspacesWithFaveProp } = useMemo(() => {
|
const { workspacesWithFaveProp } = useMemo(() => {
|
||||||
const workspacesWithFav = filteredWorkspaces
|
const workspacesWithFav = filteredWorkspaces
|
||||||
.map((w): Workspace & { isFavorite: boolean } => ({
|
.map((w): Workspace & { isFavorite: boolean } => ({
|
||||||
...w,
|
...w,
|
||||||
isFavorite: Boolean(projectFavorites?.includes(w.id)),
|
isFavorite: Boolean(projectFavorites?.includes(w.id))
|
||||||
}))
|
}))
|
||||||
.sort((a, b) => Number(b.isFavorite) - Number(a.isFavorite))
|
.sort((a, b) => Number(b.isFavorite) - Number(a.isFavorite))
|
||||||
.slice(offset, limit * page)
|
.slice(offset, limit * page);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
workspacesWithFaveProp: workspacesWithFav,
|
workspacesWithFaveProp: workspacesWithFav
|
||||||
}
|
};
|
||||||
}, [filteredWorkspaces, projectFavorites])
|
}, [filteredWorkspaces, projectFavorites]);
|
||||||
|
|
||||||
const addProjectToFavorites = async (projectId: string) => {
|
const addProjectToFavorites = async (projectId: string) => {
|
||||||
try {
|
try {
|
||||||
if (currentOrg?.id) {
|
if (currentOrg?.id) {
|
||||||
await updateUserProjectFavorites({
|
await updateUserProjectFavorites({
|
||||||
orgId: currentOrg?.id,
|
orgId: currentOrg?.id,
|
||||||
projectFavorites: [...(projectFavorites || []), projectId],
|
projectFavorites: [...(projectFavorites || []), projectId]
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
createNotification({
|
createNotification({
|
||||||
text: 'Failed to add project to favorites.',
|
text: "Failed to add project to favorites.",
|
||||||
type: 'error',
|
type: "error"
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
const removeProjectFromFavorites = async (projectId: string) => {
|
const removeProjectFromFavorites = async (projectId: string) => {
|
||||||
try {
|
try {
|
||||||
if (currentOrg?.id) {
|
if (currentOrg?.id) {
|
||||||
await updateUserProjectFavorites({
|
await updateUserProjectFavorites({
|
||||||
orgId: currentOrg?.id,
|
orgId: currentOrg?.id,
|
||||||
projectFavorites: [
|
projectFavorites: [...(projectFavorites || []).filter((entry) => entry !== projectId)]
|
||||||
...(projectFavorites || []).filter((entry) => entry !== projectId),
|
});
|
||||||
],
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
createNotification({
|
createNotification({
|
||||||
text: 'Failed to remove project from favorites.',
|
text: "Failed to remove project from favorites.",
|
||||||
type: 'error',
|
type: "error"
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
const renderProjectGridItem = (workspace: Workspace, isFavorite: boolean) => (
|
const renderProjectGridItem = (workspace: Workspace, isFavorite: boolean) => (
|
||||||
// eslint-disable-next-line jsx-a11y/no-static-element-interactions, jsx-a11y/click-events-have-key-events
|
// 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={() => {
|
onClick={() => {
|
||||||
// TODO(rbr): fix this
|
// TODO(rbr): fix this
|
||||||
navigate({
|
navigate({
|
||||||
to: getProjectHomePage(workspace),
|
to: getProjectHomePage(workspace)
|
||||||
})
|
});
|
||||||
localStorage.setItem('projectData.id', workspace.id)
|
localStorage.setItem("projectData.id", workspace.id);
|
||||||
}}
|
}}
|
||||||
key={workspace.id}
|
key={workspace.id}
|
||||||
className="flex h-40 min-w-72 cursor-pointer flex-col rounded-md border border-mineshaft-600 bg-mineshaft-800 p-4"
|
className="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="flex flex-row justify-between">
|
||||||
<div className="mt-0 truncate text-lg text-mineshaft-100">
|
<div className="mt-0 truncate text-lg text-mineshaft-100">{workspace.name}</div>
|
||||||
{workspace.name}
|
|
||||||
</div>
|
|
||||||
{isFavorite ? (
|
{isFavorite ? (
|
||||||
<FontAwesomeIcon
|
<FontAwesomeIcon
|
||||||
icon={faSolidStar}
|
icon={faSolidStar}
|
||||||
className="text-sm text-yellow-600 hover:text-mineshaft-400"
|
className="text-sm text-yellow-600 hover:text-mineshaft-400"
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.stopPropagation()
|
e.stopPropagation();
|
||||||
removeProjectFromFavorites(workspace.id)
|
removeProjectFromFavorites(workspace.id);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
@@ -223,8 +214,8 @@ export const ProductOverview = ({ type }: Props) => {
|
|||||||
icon={faStar}
|
icon={faStar}
|
||||||
className="text-sm text-mineshaft-400 hover:text-mineshaft-300"
|
className="text-sm text-mineshaft-400 hover:text-mineshaft-300"
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.stopPropagation()
|
e.stopPropagation();
|
||||||
addProjectToFavorites(workspace.id)
|
addProjectToFavorites(workspace.id);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
@@ -233,10 +224,10 @@ export const ProductOverview = ({ type }: Props) => {
|
|||||||
<div
|
<div
|
||||||
className="mb-2.5 mt-1 grow text-sm text-mineshaft-300"
|
className="mb-2.5 mt-1 grow text-sm text-mineshaft-300"
|
||||||
style={{
|
style={{
|
||||||
overflow: 'hidden',
|
overflow: "hidden",
|
||||||
display: '-webkit-box',
|
display: "-webkit-box",
|
||||||
WebkitBoxOrient: 'vertical',
|
WebkitBoxOrient: "vertical",
|
||||||
WebkitLineClamp: 2,
|
WebkitLineClamp: 2
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{workspace.description}
|
{workspace.description}
|
||||||
@@ -250,7 +241,7 @@ export const ProductOverview = ({ type }: Props) => {
|
|||||||
)}
|
)}
|
||||||
<button type="button">
|
<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">
|
<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
|
<FontAwesomeIcon
|
||||||
icon={faArrowRight}
|
icon={faArrowRight}
|
||||||
className="pl-1.5 pr-0.5 duration-200 hover:pl-2 hover:pr-0"
|
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>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
);
|
||||||
|
|
||||||
const renderProjectListItem = (
|
const renderProjectListItem = (workspace: Workspace, isFavorite: boolean, index: number) => (
|
||||||
workspace: Workspace,
|
|
||||||
isFavorite: boolean,
|
|
||||||
index: number,
|
|
||||||
) => (
|
|
||||||
// eslint-disable-next-line jsx-a11y/no-static-element-interactions, jsx-a11y/click-events-have-key-events
|
// eslint-disable-next-line jsx-a11y/no-static-element-interactions, jsx-a11y/click-events-have-key-events
|
||||||
<div
|
<div
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
// TODO(rbr): fix this
|
// TODO(rbr): fix this
|
||||||
navigate({
|
navigate({
|
||||||
to: getProjectHomePage(workspace),
|
to: getProjectHomePage(workspace)
|
||||||
})
|
});
|
||||||
localStorage.setItem('projectData.id', workspace.id)
|
localStorage.setItem("projectData.id", workspace.id);
|
||||||
}}
|
}}
|
||||||
key={workspace.id}
|
key={workspace.id}
|
||||||
className={`group grid h-14 min-w-72 cursor-pointer grid-cols-6 border-l border-r border-t border-mineshaft-600 bg-mineshaft-800 px-6 hover:bg-mineshaft-700 ${
|
className={`group 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="flex items-center sm:col-span-3 lg:col-span-4">
|
||||||
<div className="truncate text-sm text-mineshaft-100">
|
<div className="truncate text-sm text-mineshaft-100">{workspace.name}</div>
|
||||||
{workspace.name}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center justify-end sm:col-span-3 lg:col-span-2">
|
<div className="flex items-center justify-end sm:col-span-3 lg:col-span-2">
|
||||||
<div className="text-center text-sm text-mineshaft-300">
|
<div className="text-center text-sm text-mineshaft-300">
|
||||||
@@ -294,8 +279,8 @@ export const ProductOverview = ({ type }: Props) => {
|
|||||||
icon={faSolidStar}
|
icon={faSolidStar}
|
||||||
className="ml-6 text-sm text-yellow-600 hover:text-mineshaft-400"
|
className="ml-6 text-sm text-yellow-600 hover:text-mineshaft-400"
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.stopPropagation()
|
e.stopPropagation();
|
||||||
removeProjectFromFavorites(workspace.id)
|
removeProjectFromFavorites(workspace.id);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
@@ -303,16 +288,16 @@ export const ProductOverview = ({ type }: Props) => {
|
|||||||
icon={faStar}
|
icon={faStar}
|
||||||
className="ml-6 text-sm text-mineshaft-400 hover:text-mineshaft-300"
|
className="ml-6 text-sm text-mineshaft-400 hover:text-mineshaft-300"
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.stopPropagation()
|
e.stopPropagation();
|
||||||
addProjectToFavorites(workspace.id)
|
addProjectToFavorites(workspace.id);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
);
|
||||||
|
|
||||||
let projectsComponents: ReactNode
|
let projectsComponents: ReactNode;
|
||||||
|
|
||||||
if (filteredWorkspaces.length || isProjectViewLoading) {
|
if (filteredWorkspaces.length || isProjectViewLoading) {
|
||||||
switch (projectsViewMode) {
|
switch (projectsViewMode) {
|
||||||
@@ -339,14 +324,14 @@ export const ProductOverview = ({ type }: Props) => {
|
|||||||
{!isProjectViewLoading && (
|
{!isProjectViewLoading && (
|
||||||
<>
|
<>
|
||||||
{workspacesWithFaveProp.map((workspace) =>
|
{workspacesWithFaveProp.map((workspace) =>
|
||||||
renderProjectGridItem(workspace, workspace.isFavorite),
|
renderProjectGridItem(workspace, workspace.isFavorite)
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)
|
);
|
||||||
|
|
||||||
break
|
break;
|
||||||
case ProjectsViewMode.LIST:
|
case ProjectsViewMode.LIST:
|
||||||
default:
|
default:
|
||||||
projectsComponents = (
|
projectsComponents = (
|
||||||
@@ -356,19 +341,19 @@ export const ProductOverview = ({ type }: Props) => {
|
|||||||
<div
|
<div
|
||||||
key={`workspace-cards-loading-${i + 1}`}
|
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 ${
|
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 === 0 && "rounded-t-md"
|
||||||
} ${i === 2 && 'rounded-b-md border-b'}`}
|
} ${i === 2 && "rounded-b-md border-b"}`}
|
||||||
>
|
>
|
||||||
<Skeleton className="w-full bg-mineshaft-600" />
|
<Skeleton className="w-full bg-mineshaft-600" />
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
{!isProjectViewLoading &&
|
{!isProjectViewLoading &&
|
||||||
workspacesWithFaveProp.map((workspace, ind) =>
|
workspacesWithFaveProp.map((workspace, ind) =>
|
||||||
renderProjectListItem(workspace, workspace.isFavorite, ind),
|
renderProjectListItem(workspace, workspace.isFavorite, ind)
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)
|
);
|
||||||
break
|
break;
|
||||||
}
|
}
|
||||||
} else if (orgWorkspaces.length && searchFilter) {
|
} else if (orgWorkspaces.length && searchFilter) {
|
||||||
projectsComponents = (
|
projectsComponents = (
|
||||||
@@ -377,19 +362,15 @@ export const ProductOverview = ({ type }: Props) => {
|
|||||||
icon={faSearch}
|
icon={faSearch}
|
||||||
className="mb-4 mt-2 w-full text-center text-5xl text-mineshaft-400"
|
className="mb-4 mt-2 w-full text-center text-5xl text-mineshaft-400"
|
||||||
/>
|
/>
|
||||||
<div className="text-center font-light">
|
<div className="text-center font-light">No projects match search...</div>
|
||||||
No projects match search...
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="mx-auto flex max-w-7xl flex-col justify-start bg-bunker-800 md:h-screen">
|
<div className="mx-auto flex max-w-7xl flex-col justify-start bg-bunker-800 md:h-screen">
|
||||||
<Helmet>
|
<Helmet>
|
||||||
<title>
|
<title>{t("common.head-title", { title: t("settings.members.title") })}</title>
|
||||||
{t('common.head-title', { title: t('settings.members.title') })}
|
|
||||||
</title>
|
|
||||||
<link rel="icon" href="/infisical.ico" />
|
<link rel="icon" href="/infisical.ico" />
|
||||||
</Helmet>
|
</Helmet>
|
||||||
{!serverDetails?.redisConfigured && (
|
{!serverDetails?.redisConfigured && (
|
||||||
@@ -400,8 +381,8 @@ export const ProductOverview = ({ type }: Props) => {
|
|||||||
icon={faExclamationCircle}
|
icon={faExclamationCircle}
|
||||||
className="mr-4 p-4 text-2xl text-mineshaft-50"
|
className="mr-4 p-4 text-2xl text-mineshaft-50"
|
||||||
/>
|
/>
|
||||||
Attention: Updated versions of Infisical now require Redis for full
|
Attention: Updated versions of Infisical now require Redis for full functionality. Learn
|
||||||
functionality. Learn how to configure it
|
how to configure it
|
||||||
<a
|
<a
|
||||||
href="https://infisical.com/docs/self-hosting/configuration/redis"
|
href="https://infisical.com/docs/self-hosting/configuration/redis"
|
||||||
rel="noopener noreferrer"
|
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="mb-4 flex flex-col items-start justify-start px-6 py-6 pb-0">
|
||||||
<div className="flex w-full justify-between">
|
<div className="flex w-full justify-between">
|
||||||
<p className="mr-4 text-3xl font-semibold text-white">
|
<p className="mr-4 text-3xl font-semibold text-white">{formatTitle(type)}</p>
|
||||||
{formatTitle(type)}
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<p className="mr-4 mt-2 text-gray-400">{formatDescription(type)}</p>
|
<p className="mr-4 mt-2 text-gray-400">{formatDescription(type)}</p>
|
||||||
@@ -437,9 +416,7 @@ export const ProductOverview = ({ type }: Props) => {
|
|||||||
<IconButton
|
<IconButton
|
||||||
className="min-w-[2.4rem] border-none hover:bg-mineshaft-600"
|
className="min-w-[2.4rem] border-none hover:bg-mineshaft-600"
|
||||||
ariaLabel={`Sort ${
|
ariaLabel={`Sort ${
|
||||||
orderDirection === OrderByDirection.ASC
|
orderDirection === OrderByDirection.ASC ? "descending" : "ascending"
|
||||||
? 'descending'
|
|
||||||
: 'ascending'
|
|
||||||
}`}
|
}`}
|
||||||
variant="plain"
|
variant="plain"
|
||||||
size="xs"
|
size="xs"
|
||||||
@@ -447,11 +424,7 @@ export const ProductOverview = ({ type }: Props) => {
|
|||||||
onClick={toggleOrderDirection}
|
onClick={toggleOrderDirection}
|
||||||
>
|
>
|
||||||
<FontAwesomeIcon
|
<FontAwesomeIcon
|
||||||
icon={
|
icon={orderDirection === OrderByDirection.ASC ? faArrowDownAZ : faArrowUpZA}
|
||||||
orderDirection === OrderByDirection.ASC
|
|
||||||
? faArrowDownAZ
|
|
||||||
: faArrowUpZA
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
@@ -460,15 +433,13 @@ export const ProductOverview = ({ type }: Props) => {
|
|||||||
<IconButton
|
<IconButton
|
||||||
variant="outline_bg"
|
variant="outline_bg"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
localStorage.setItem('projectsViewMode', ProjectsViewMode.GRID)
|
localStorage.setItem("projectsViewMode", ProjectsViewMode.GRID);
|
||||||
setProjectsViewMode(ProjectsViewMode.GRID)
|
setProjectsViewMode(ProjectsViewMode.GRID);
|
||||||
}}
|
}}
|
||||||
ariaLabel="grid"
|
ariaLabel="grid"
|
||||||
size="xs"
|
size="xs"
|
||||||
className={`${
|
className={`${
|
||||||
projectsViewMode === ProjectsViewMode.GRID
|
projectsViewMode === ProjectsViewMode.GRID ? "bg-mineshaft-500" : "bg-transparent"
|
||||||
? 'bg-mineshaft-500'
|
|
||||||
: 'bg-transparent'
|
|
||||||
} min-w-[2.4rem] border-none hover:bg-mineshaft-600`}
|
} min-w-[2.4rem] border-none hover:bg-mineshaft-600`}
|
||||||
>
|
>
|
||||||
<FontAwesomeIcon icon={faBorderAll} />
|
<FontAwesomeIcon icon={faBorderAll} />
|
||||||
@@ -476,24 +447,19 @@ export const ProductOverview = ({ type }: Props) => {
|
|||||||
<IconButton
|
<IconButton
|
||||||
variant="outline_bg"
|
variant="outline_bg"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
localStorage.setItem('projectsViewMode', ProjectsViewMode.LIST)
|
localStorage.setItem("projectsViewMode", ProjectsViewMode.LIST);
|
||||||
setProjectsViewMode(ProjectsViewMode.LIST)
|
setProjectsViewMode(ProjectsViewMode.LIST);
|
||||||
}}
|
}}
|
||||||
ariaLabel="list"
|
ariaLabel="list"
|
||||||
size="xs"
|
size="xs"
|
||||||
className={`${
|
className={`${
|
||||||
projectsViewMode === ProjectsViewMode.LIST
|
projectsViewMode === ProjectsViewMode.LIST ? "bg-mineshaft-500" : "bg-transparent"
|
||||||
? 'bg-mineshaft-500'
|
|
||||||
: 'bg-transparent'
|
|
||||||
} min-w-[2.4rem] border-none hover:bg-mineshaft-600`}
|
} min-w-[2.4rem] border-none hover:bg-mineshaft-600`}
|
||||||
>
|
>
|
||||||
<FontAwesomeIcon icon={faList} />
|
<FontAwesomeIcon icon={faList} />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</div>
|
</div>
|
||||||
<OrgPermissionCan
|
<OrgPermissionCan I={OrgPermissionActions.Create} an={OrgPermissionSubjects.Workspace}>
|
||||||
I={OrgPermissionActions.Create}
|
|
||||||
an={OrgPermissionSubjects.Workspace}
|
|
||||||
>
|
|
||||||
{(isAllowed) => (
|
{(isAllowed) => (
|
||||||
<Button
|
<Button
|
||||||
isDisabled={!isAllowed}
|
isDisabled={!isAllowed}
|
||||||
@@ -501,9 +467,9 @@ export const ProductOverview = ({ type }: Props) => {
|
|||||||
leftIcon={<FontAwesomeIcon icon={faPlus} />}
|
leftIcon={<FontAwesomeIcon icon={faPlus} />}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (isAddingProjectsAllowed) {
|
if (isAddingProjectsAllowed) {
|
||||||
handlePopUpOpen('addNewWs')
|
handlePopUpOpen("addNewWs");
|
||||||
} else {
|
} else {
|
||||||
handlePopUpOpen('upgradePlan')
|
handlePopUpOpen("upgradePlan");
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
className="ml-2"
|
className="ml-2"
|
||||||
@@ -518,8 +484,8 @@ export const ProductOverview = ({ type }: Props) => {
|
|||||||
<Pagination
|
<Pagination
|
||||||
className={
|
className={
|
||||||
projectsViewMode === ProjectsViewMode.GRID
|
projectsViewMode === ProjectsViewMode.GRID
|
||||||
? 'col-span-full !justify-start border-transparent bg-transparent pl-2'
|
? "col-span-full !justify-start border-transparent bg-transparent pl-2"
|
||||||
: 'rounded-b-md border border-mineshaft-600'
|
: "rounded-b-md border border-mineshaft-600"
|
||||||
}
|
}
|
||||||
perPage={perPage}
|
perPage={perPage}
|
||||||
perPageList={[12, 24, 48, 96]}
|
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"
|
className="mb-4 mt-2 w-full text-center text-5xl text-mineshaft-400"
|
||||||
/>
|
/>
|
||||||
<div className="text-center font-light">
|
<div className="text-center font-light">
|
||||||
You are not part of any projects in this organization yet. When
|
You are not part of any projects in this organization yet. When you are, they will
|
||||||
you are, they will appear here.
|
appear here.
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-0.5 text-center font-light">
|
<div className="mt-0.5 text-center font-light">
|
||||||
Create a new project, or ask other organization members to give
|
Create a new project, or ask other organization members to give you necessary
|
||||||
you necessary permissions.
|
permissions.
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<NewProjectModal
|
<NewProjectModal
|
||||||
isOpen={popUp.addNewWs.isOpen}
|
isOpen={popUp.addNewWs.isOpen}
|
||||||
onOpenChange={(isOpen) => handlePopUpToggle('addNewWs', isOpen)}
|
onOpenChange={(isOpen) => handlePopUpToggle("addNewWs", isOpen)}
|
||||||
projectType={type}
|
projectType={type}
|
||||||
/>
|
/>
|
||||||
<UpgradePlanModal
|
<UpgradePlanModal
|
||||||
isOpen={popUp.upgradePlan.isOpen}
|
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."
|
text="You have exceeded the number of projects allowed on the free plan."
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
const SecretManagerOverviewPage = () => (
|
const SecretManagerOverviewPage = () => <ProductOverview type={ProjectType.SecretManager} />;
|
||||||
<ProductOverview type={ProjectType.SecretManager} />
|
|
||||||
)
|
|
||||||
|
|
||||||
export const Route = createFileRoute(
|
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
|
||||||
})
|
});
|
||||||
|
|||||||
+55
-60
@@ -1,73 +1,68 @@
|
|||||||
import { useEffect } from 'react'
|
import { useEffect } from "react";
|
||||||
import { Helmet } from 'react-helmet'
|
import { Helmet } from "react-helmet";
|
||||||
import { createFileRoute, useSearch } from '@tanstack/react-router'
|
import { createFileRoute, useSearch } from "@tanstack/react-router";
|
||||||
|
import { zodValidator } from "@tanstack/zod-adapter";
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
import { OrgPermissionCan } from '@app/components/permissions'
|
import { OrgPermissionCan } from "@app/components/permissions";
|
||||||
import { Button, NoticeBanner } from '@app/components/v2'
|
import { Button, NoticeBanner } from "@app/components/v2";
|
||||||
import {
|
import {
|
||||||
OrgPermissionActions,
|
OrgPermissionActions,
|
||||||
OrgPermissionSubjects,
|
OrgPermissionSubjects,
|
||||||
useOrganization,
|
useOrganization,
|
||||||
useServerConfig,
|
useServerConfig
|
||||||
} from '@app/context'
|
} from "@app/context";
|
||||||
import { withPermission } from '@app/hoc'
|
import { withPermission } from "@app/hoc";
|
||||||
import {
|
import {
|
||||||
useCreateNewInstallationSession,
|
useCreateNewInstallationSession,
|
||||||
useGetSecretScanningInstallationStatus,
|
useGetSecretScanningInstallationStatus,
|
||||||
useLinkGitAppInstallationWithOrg,
|
useLinkGitAppInstallationWithOrg
|
||||||
} from '@app/hooks/api/secretScanning'
|
} from "@app/hooks/api/secretScanning";
|
||||||
|
|
||||||
import { SecretScanningLogsTable } from './-components'
|
import { SecretScanningLogsTable } from "./-components";
|
||||||
|
|
||||||
const SecretScanning = withPermission(
|
const SecretScanning = withPermission(
|
||||||
() => {
|
() => {
|
||||||
const queryParams = useSearch({
|
const queryParams = useSearch({
|
||||||
from: '/_authenticate/_org_details/_org-layout/organization/$organizationId/secret-scanning',
|
from: "/_authenticate/_org_details/_org-layout/organization/$organizationId/secret-scanning/"
|
||||||
})
|
});
|
||||||
const { config } = useServerConfig()
|
const { config } = useServerConfig();
|
||||||
const { currentOrg } = useOrganization()
|
const { currentOrg } = useOrganization();
|
||||||
const organizationId = currentOrg.id
|
const organizationId = currentOrg.id;
|
||||||
|
|
||||||
const { mutateAsync: linkGitAppInstallationWithOrganization } =
|
const { mutateAsync: linkGitAppInstallationWithOrganization } =
|
||||||
useLinkGitAppInstallationWithOrg()
|
useLinkGitAppInstallationWithOrg();
|
||||||
const { mutateAsync: createNewIntegrationSession } =
|
const { mutateAsync: createNewIntegrationSession } = useCreateNewInstallationSession();
|
||||||
useCreateNewInstallationSession()
|
const { data: installationStatus, isLoading: isSecretScanningInstatllationStatusLoading } =
|
||||||
const {
|
useGetSecretScanningInstallationStatus(organizationId);
|
||||||
data: installationStatus,
|
|
||||||
isLoading: isSecretScanningInstatllationStatusLoading,
|
|
||||||
} = useGetSecretScanningInstallationStatus(organizationId)
|
|
||||||
const integrationEnabled =
|
const integrationEnabled =
|
||||||
!isSecretScanningInstatllationStatusLoading &&
|
!isSecretScanningInstatllationStatusLoading && installationStatus?.appInstallationCompleted;
|
||||||
installationStatus?.appInstallationCompleted
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const linkInstallation = async () => {
|
const linkInstallation = async () => {
|
||||||
if (
|
if (queryParams.state && queryParams.installation_id) {
|
||||||
typeof queryParams.state === 'string' &&
|
|
||||||
typeof queryParams.installation_id === 'string'
|
|
||||||
) {
|
|
||||||
try {
|
try {
|
||||||
const isLinked = await linkGitAppInstallationWithOrganization({
|
const isLinked = await linkGitAppInstallationWithOrganization({
|
||||||
installationId: queryParams.installation_id as string,
|
installationId: queryParams.installation_id as string,
|
||||||
sessionId: queryParams.state as string,
|
sessionId: queryParams.state as string
|
||||||
})
|
});
|
||||||
if (isLinked) {
|
if (isLinked) {
|
||||||
window.location.reload()
|
window.location.reload();
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('installation verification complete')
|
console.log("installation verification complete");
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log('app installation is stale, start new session', e)
|
console.log("app installation is stale, start new session", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
linkInstallation()
|
linkInstallation();
|
||||||
}, [queryParams.state, queryParams.installation_id])
|
}, [queryParams.state, queryParams.installation_id]);
|
||||||
|
|
||||||
const generateNewIntegrationSession = async () => {
|
const generateNewIntegrationSession = async () => {
|
||||||
const session = await createNewIntegrationSession({ organizationId })
|
const session = await createNewIntegrationSession({ organizationId });
|
||||||
window.location.href = `https://github.com/apps/infisical-radar/installations/new?state=${session.sessionId}`
|
window.location.href = `https://github.com/apps/infisical-radar/installations/new?state=${session.sessionId}`;
|
||||||
}
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
@@ -78,26 +73,20 @@ const SecretScanning = withPermission(
|
|||||||
</Helmet>
|
</Helmet>
|
||||||
<div className="flex h-full w-full justify-center bg-bunker-800 text-white">
|
<div className="flex h-full w-full justify-center bg-bunker-800 text-white">
|
||||||
<div className="w-full max-w-7xl px-6">
|
<div className="w-full max-w-7xl px-6">
|
||||||
<div className="mt-6 text-3xl font-semibold text-gray-200">
|
<div className="mt-6 text-3xl font-semibold text-gray-200">Secret Scanning</div>
|
||||||
Secret Scanning
|
|
||||||
</div>
|
|
||||||
<div className="mb-6 text-lg text-mineshaft-300">
|
<div className="mb-6 text-lg text-mineshaft-300">
|
||||||
Automatically monitor your GitHub activity and prevent secret
|
Automatically monitor your GitHub activity and prevent secret leaks
|
||||||
leaks
|
|
||||||
</div>
|
</div>
|
||||||
{config.isSecretScanningDisabled && (
|
{config.isSecretScanningDisabled && (
|
||||||
<NoticeBanner
|
<NoticeBanner title="Secret scanning is in maintenance" className="mb-4">
|
||||||
title="Secret scanning is in maintenance"
|
We are working on improving the performance of secret scanning due to increased
|
||||||
className="mb-4"
|
usage.
|
||||||
>
|
|
||||||
We are working on improving the performance of secret scanning
|
|
||||||
due to increased usage.
|
|
||||||
</NoticeBanner>
|
</NoticeBanner>
|
||||||
)}
|
)}
|
||||||
<div className="relative mb-6 flex justify-between rounded-md border border-mineshaft-600 bg-mineshaft-800 p-6">
|
<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="flex flex-col items-start">
|
||||||
<div className="mb-1 flex flex-row">
|
<div className="mb-1 flex flex-row">
|
||||||
Secret Scanning Status:{' '}
|
Secret Scanning Status:{" "}
|
||||||
{integrationEnabled ? (
|
{integrationEnabled ? (
|
||||||
<p className="ml-1.5 font-semibold text-green">Enabled</p>
|
<p className="ml-1.5 font-semibold text-green">Enabled</p>
|
||||||
) : (
|
) : (
|
||||||
@@ -107,8 +96,8 @@ const SecretScanning = withPermission(
|
|||||||
<div>
|
<div>
|
||||||
{integrationEnabled ? (
|
{integrationEnabled ? (
|
||||||
<p className="text-mineshaft-300">
|
<p className="text-mineshaft-300">
|
||||||
Your GitHub organization is connected to Infisical, and is
|
Your GitHub organization is connected to Infisical, and is being continuously
|
||||||
being continuously monitored for secret leaks.
|
monitored for secret leaks.
|
||||||
</p>
|
</p>
|
||||||
) : (
|
) : (
|
||||||
<p className="text-mineshaft-300">
|
<p className="text-mineshaft-300">
|
||||||
@@ -148,16 +137,22 @@ const SecretScanning = withPermission(
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
);
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
action: OrgPermissionActions.Read,
|
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(
|
export const Route = createFileRoute(
|
||||||
'/_authenticate/_org_details/_org-layout/organization/$organizationId/secret-scanning/',
|
"/_authenticate/_org_details/_org-layout/organization/$organizationId/secret-scanning/"
|
||||||
)({
|
)({
|
||||||
component: SecretScanning,
|
component: SecretScanning,
|
||||||
})
|
validateSearch: zodValidator(SecretScanningQueryParams)
|
||||||
|
});
|
||||||
|
|||||||
+1
-1
@@ -3,6 +3,7 @@ import { Controller, useForm } from "react-hook-form";
|
|||||||
import { faUsers, faXmark } from "@fortawesome/free-solid-svg-icons";
|
import { faUsers, faXmark } from "@fortawesome/free-solid-svg-icons";
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
|
import { useNavigate } from "@tanstack/react-router";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import { createNotification } from "@app/components/notifications";
|
import { createNotification } from "@app/components/notifications";
|
||||||
@@ -35,7 +36,6 @@ import {
|
|||||||
useGetOrganizationGroups
|
useGetOrganizationGroups
|
||||||
} from "@app/hooks/api";
|
} from "@app/hooks/api";
|
||||||
import { UsePopUpState } from "@app/hooks/usePopUp";
|
import { UsePopUpState } from "@app/hooks/usePopUp";
|
||||||
import { useNavigate } from "@tanstack/react-router";
|
|
||||||
|
|
||||||
const schema = z.object({
|
const schema = z.object({
|
||||||
ldapGroupCN: z.string().min(1, "LDAP Group CN is required"),
|
ldapGroupCN: z.string().min(1, "LDAP Group CN is required"),
|
||||||
|
|||||||
+1
-1
@@ -24,7 +24,7 @@ const tabs = [
|
|||||||
];
|
];
|
||||||
export const OrgTabGroup = () => {
|
export const OrgTabGroup = () => {
|
||||||
const search = useSearch({
|
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);
|
const [selectedTab, setSelectedTab] = useState(search.selectedTab || tabs[0].key);
|
||||||
|
|
||||||
|
|||||||
-2
@@ -1,7 +1,6 @@
|
|||||||
import { faSlack } from "@fortawesome/free-brands-svg-icons";
|
import { faSlack } from "@fortawesome/free-brands-svg-icons";
|
||||||
import { faEllipsis, faGear, faPlus } from "@fortawesome/free-solid-svg-icons";
|
import { faEllipsis, faGear, faPlus } from "@fortawesome/free-solid-svg-icons";
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
import { useNavigate } from "@tanstack/react-router";
|
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import { twMerge } from "tailwind-merge";
|
import { twMerge } from "tailwind-merge";
|
||||||
|
|
||||||
@@ -45,7 +44,6 @@ export const OrgWorkflowIntegrationTab = withPermission(
|
|||||||
] as const);
|
] as const);
|
||||||
|
|
||||||
const { currentOrg } = useOrganization();
|
const { currentOrg } = useOrganization();
|
||||||
const navigate = useNavigate();
|
|
||||||
const { data: workflowIntegrations, isLoading: isWorkflowIntegrationsLoading } =
|
const { data: workflowIntegrations, isLoading: isWorkflowIntegrationsLoading } =
|
||||||
useGetWorkflowIntegrations(currentOrg?.id);
|
useGetWorkflowIntegrations(currentOrg?.id);
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,7 @@
|
|||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
import { Controller, useForm } from "react-hook-form";
|
import { Controller, useForm } from "react-hook-form";
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
|
import { useNavigate } from "@tanstack/react-router";
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
@@ -14,7 +15,6 @@ import {
|
|||||||
useUpdateSlackIntegration
|
useUpdateSlackIntegration
|
||||||
} from "@app/hooks/api";
|
} from "@app/hooks/api";
|
||||||
import { slugSchema } from "@app/lib/schemas";
|
import { slugSchema } from "@app/lib/schemas";
|
||||||
import { useNavigate } from "@tanstack/react-router";
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
id?: string;
|
id?: string;
|
||||||
|
|||||||
+19
-18
@@ -1,33 +1,29 @@
|
|||||||
import { Helmet } from 'react-helmet'
|
import { Helmet } from "react-helmet";
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from "react-i18next";
|
||||||
import { createFileRoute } from '@tanstack/react-router'
|
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 SettingsOrg = () => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Helmet>
|
<Helmet>
|
||||||
<title>
|
<title>{t("common.head-title", { title: t("settings.org.title") })}</title>
|
||||||
{t('common.head-title', { title: t('settings.org.title') })}
|
|
||||||
</title>
|
|
||||||
<link rel="icon" href="/infisical.ico" />
|
<link rel="icon" href="/infisical.ico" />
|
||||||
</Helmet>
|
</Helmet>
|
||||||
<div className="flex w-full justify-center bg-bunker-800 py-6 text-white">
|
<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="w-full max-w-4xl px-6">
|
||||||
<div className="mb-4">
|
<div className="mb-4">
|
||||||
<p className="text-3xl font-semibold text-gray-200">
|
<p className="text-3xl font-semibold text-gray-200">{t("settings.org.title")}</p>
|
||||||
{t('settings.org.title')}
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="flex w-full justify-center bg-bunker-800 py-6 text-white">
|
<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="w-full max-w-4xl px-6">
|
||||||
<div className="mb-4">
|
<div className="mb-4">
|
||||||
<p className="text-3xl font-semibold text-gray-200">
|
<p className="text-3xl font-semibold text-gray-200">{t("settings.org.title")}</p>
|
||||||
{t('settings.org.title')}
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
<OrgTabGroup />
|
<OrgTabGroup />
|
||||||
</div>
|
</div>
|
||||||
@@ -35,11 +31,16 @@ const SettingsOrg = () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
)
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
|
export const SettingsOrgQueryParams = z.object({
|
||||||
|
selectedTab: z.string().catch("")
|
||||||
|
});
|
||||||
|
|
||||||
export const Route = createFileRoute(
|
export const Route = createFileRoute(
|
||||||
'/_authenticate/_org_details/_org-layout/organization/$organizationId/settings/',
|
"/_authenticate/_org_details/_org-layout/organization/$organizationId/settings/"
|
||||||
)({
|
)({
|
||||||
component: SettingsOrg,
|
component: SettingsOrg,
|
||||||
})
|
validateSearch: zodValidator(SettingsOrgQueryParams)
|
||||||
|
});
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
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/")({
|
export const Route = createFileRoute("/_authenticate/_org_details/_org-layout/organization/")({
|
||||||
beforeLoad: ({ context }) => {
|
beforeLoad: ({ context }) => {
|
||||||
redirect({
|
redirect({
|
||||||
throw: true,
|
throw: true,
|
||||||
to: "/organization/$organizationId/secret-manager",
|
to: `/organization/$organizationId/${ProjectType.SecretManager}/overview` as const,
|
||||||
params: {
|
params: {
|
||||||
organizationId: String(context.organizationId)
|
organizationId: String(context.organizationId)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
import { Helmet } from "react-helmet";
|
import { Helmet } from "react-helmet";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { createFileRoute } from "@tanstack/react-router";
|
||||||
|
|
||||||
import { CreateOrgModal } from "@app/components/organization/CreateOrgModal";
|
import { CreateOrgModal } from "@app/components/organization/CreateOrgModal";
|
||||||
import { usePopUp } from "@app/hooks";
|
import { usePopUp } from "@app/hooks";
|
||||||
import { createFileRoute } from "@tanstack/react-router";
|
|
||||||
|
|
||||||
const NoneOrganization = () => {
|
const NoneOrganization = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { createFileRoute, redirect } from "@tanstack/react-router";
|
|||||||
|
|
||||||
import { authKeys, fetchAuthToken } from "@app/hooks/api/auth/queries";
|
import { authKeys, fetchAuthToken } from "@app/hooks/api/auth/queries";
|
||||||
import { setAuthToken } from "@app/hooks/api/reactQuery";
|
import { setAuthToken } from "@app/hooks/api/reactQuery";
|
||||||
|
import { ProjectType } from "@app/hooks/api/workspace/types";
|
||||||
|
|
||||||
export const Route = createFileRoute("/_restrict_login_signup")({
|
export const Route = createFileRoute("/_restrict_login_signup")({
|
||||||
beforeLoad: async ({ context }) => {
|
beforeLoad: async ({ context }) => {
|
||||||
@@ -20,7 +21,7 @@ export const Route = createFileRoute("/_restrict_login_signup")({
|
|||||||
throw redirect({ to: "/login/select-organization" });
|
throw redirect({ to: "/login/select-organization" });
|
||||||
}
|
}
|
||||||
throw redirect({
|
throw redirect({
|
||||||
to: "/organization/$organizationId/secret-manager",
|
to: `/organization/$organizationId/${ProjectType.SecretManager}/overview` as const,
|
||||||
params: {
|
params: {
|
||||||
organizationId: data.organizationId
|
organizationId: data.organizationId
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ export const navigateUserToOrg = async (navigate: NavigateFn, organizationId?: s
|
|||||||
} else {
|
} else {
|
||||||
// user is not part of any non-auth enforced orgs
|
// user is not part of any non-auth enforced orgs
|
||||||
localStorage.removeItem("orgData.id");
|
localStorage.removeItem("orgData.id");
|
||||||
navigate({ to: "/org/none" });
|
navigate({ to: "/organization/none" });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -2,9 +2,11 @@ import { useEffect } from "react";
|
|||||||
import { createFileRoute, useSearch } from "@tanstack/react-router";
|
import { createFileRoute, useSearch } from "@tanstack/react-router";
|
||||||
|
|
||||||
import SecurityClient from "@app/components/utilities/SecurityClient";
|
import SecurityClient from "@app/components/utilities/SecurityClient";
|
||||||
|
import { z } from "zod";
|
||||||
|
import { zodValidator } from "@tanstack/zod-adapter";
|
||||||
|
|
||||||
const LoginProviderSuccess = () => {
|
const LoginProviderSuccess = () => {
|
||||||
const search = useSearch({ from: "/login/provider/success" });
|
const search = useSearch({ from: "/_restrict_login_signup/login/provider/success" });
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
SecurityClient.setProviderAuthToken(search.token);
|
SecurityClient.setProviderAuthToken(search.token);
|
||||||
@@ -14,6 +16,11 @@ const LoginProviderSuccess = () => {
|
|||||||
return <div />;
|
return <div />;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const LoginProviderSuccessQuerySchema = z.object({
|
||||||
|
token: z.string()
|
||||||
|
});
|
||||||
|
|
||||||
export const Route = createFileRoute("/_restrict_login_signup/login/provider/success")({
|
export const Route = createFileRoute("/_restrict_login_signup/login/provider/success")({
|
||||||
component: LoginProviderSuccess
|
component: LoginProviderSuccess,
|
||||||
|
validateSearch: zodValidator(LoginProviderSuccessQuerySchema)
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -180,7 +180,7 @@ const SelectOrganizationPage = () => {
|
|||||||
// Case: User has no organizations.
|
// 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.
|
// 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) {
|
if (organizations.data.length === 0) {
|
||||||
navigate({ to: "/org/none" });
|
navigate({ to: "/organization/none" });
|
||||||
} else if (organizations.data.length === 1) {
|
} else if (organizations.data.length === 1) {
|
||||||
if (callbackPort) {
|
if (callbackPort) {
|
||||||
handleCliRedirect();
|
handleCliRedirect();
|
||||||
|
|||||||
@@ -2,13 +2,15 @@ import { useEffect, useState } from "react";
|
|||||||
import { Helmet } from "react-helmet";
|
import { Helmet } from "react-helmet";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { createFileRoute, Link, useSearch } from "@tanstack/react-router";
|
import { createFileRoute, Link, useSearch } from "@tanstack/react-router";
|
||||||
|
import { zodValidator } from "@tanstack/zod-adapter";
|
||||||
import { jwtDecode } from "jwt-decode";
|
import { jwtDecode } from "jwt-decode";
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
import { PasswordStep } from "../-components";
|
import { PasswordStep } from "../-components";
|
||||||
|
|
||||||
const LoginSSOPage = () => {
|
const LoginSSOPage = () => {
|
||||||
const { t } = useTranslation();
|
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 token = search.token as string;
|
||||||
const [step, setStep] = useState(0);
|
const [step, setStep] = useState(0);
|
||||||
const [password, setPassword] = useState("");
|
const [password, setPassword] = useState("");
|
||||||
@@ -58,6 +60,11 @@ const LoginSSOPage = () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const LoginSSOQueryParamsSchema = z.object({
|
||||||
|
token: z.string()
|
||||||
|
});
|
||||||
|
|
||||||
export const Route = createFileRoute("/_restrict_login_signup/login/sso/")({
|
export const Route = createFileRoute("/_restrict_login_signup/login/sso/")({
|
||||||
component: LoginSSOPage
|
component: LoginSSOPage,
|
||||||
|
validateSearch: zodValidator(LoginSSOQueryParamsSchema)
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -4,7 +4,9 @@ import { FormEvent, useState } from "react";
|
|||||||
import { faCheck, faX } from "@fortawesome/free-solid-svg-icons";
|
import { faCheck, faX } from "@fortawesome/free-solid-svg-icons";
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
import { createFileRoute, useNavigate, useSearch } from "@tanstack/react-router";
|
import { createFileRoute, useNavigate, useSearch } from "@tanstack/react-router";
|
||||||
|
import { zodValidator } from "@tanstack/zod-adapter";
|
||||||
import jsrp from "jsrp";
|
import jsrp from "jsrp";
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
import InputField from "@app/components/basic/InputField";
|
import InputField from "@app/components/basic/InputField";
|
||||||
import passwordCheck from "@app/components/utilities/checks/password/PasswordCheck";
|
import passwordCheck from "@app/components/utilities/checks/password/PasswordCheck";
|
||||||
@@ -389,6 +391,12 @@ const PasswordResetPage = () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const PasswordResetPageQueryParamsSchema = z.object({
|
||||||
|
token: z.string(),
|
||||||
|
to: z.string()
|
||||||
|
});
|
||||||
|
|
||||||
export const Route = createFileRoute("/_restrict_login_signup/password-reset")({
|
export const Route = createFileRoute("/_restrict_login_signup/password-reset")({
|
||||||
component: PasswordResetPage
|
component: PasswordResetPage,
|
||||||
|
validateSearch: zodValidator(PasswordResetPageQueryParamsSchema)
|
||||||
});
|
});
|
||||||
|
|||||||
+1
-1
@@ -6,6 +6,7 @@ import jsrp from "jsrp";
|
|||||||
import nacl from "tweetnacl";
|
import nacl from "tweetnacl";
|
||||||
import { encodeBase64 } from "tweetnacl-util";
|
import { encodeBase64 } from "tweetnacl-util";
|
||||||
|
|
||||||
|
import { Mfa } from "@app/components/auth/Mfa";
|
||||||
import Aes256Gcm from "@app/components/utilities/cryptography/aes-256-gcm";
|
import Aes256Gcm from "@app/components/utilities/cryptography/aes-256-gcm";
|
||||||
import { deriveArgonKey } from "@app/components/utilities/cryptography/crypto";
|
import { deriveArgonKey } from "@app/components/utilities/cryptography/crypto";
|
||||||
import { saveTokenToLocalStorage } from "@app/components/utilities/saveTokenToLocalStorage";
|
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 { MfaMethod } from "@app/hooks/api/auth/types";
|
||||||
import { fetchOrganizations } from "@app/hooks/api/organization/queries";
|
import { fetchOrganizations } from "@app/hooks/api/organization/queries";
|
||||||
import ProjectService from "@app/services/ProjectService";
|
import ProjectService from "@app/services/ProjectService";
|
||||||
import { Mfa } from "@app/components/auth/Mfa";
|
|
||||||
|
|
||||||
// eslint-disable-next-line new-cap
|
// eslint-disable-next-line new-cap
|
||||||
const client = new jsrp.client();
|
const client = new jsrp.client();
|
||||||
|
|||||||
@@ -2,7 +2,9 @@ import { useEffect, useState } from "react";
|
|||||||
import { Helmet } from "react-helmet";
|
import { Helmet } from "react-helmet";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { createFileRoute, useSearch } from "@tanstack/react-router";
|
import { createFileRoute, useSearch } from "@tanstack/react-router";
|
||||||
|
import { zodValidator } from "@tanstack/zod-adapter";
|
||||||
import { jwtDecode } from "jwt-decode";
|
import { jwtDecode } from "jwt-decode";
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
import { BackupPDFStep } from "./-components/BackupPDFStep";
|
import { BackupPDFStep } from "./-components/BackupPDFStep";
|
||||||
import { EmailConfirmationStep } from "./-components/EmailConfirmationStep";
|
import { EmailConfirmationStep } from "./-components/EmailConfirmationStep";
|
||||||
@@ -10,7 +12,7 @@ import { UserInfoSSOStep } from "./-components/UserInfoSSOStep";
|
|||||||
|
|
||||||
const SignupSSOPage = () => {
|
const SignupSSOPage = () => {
|
||||||
const { t } = useTranslation();
|
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 token = search.token as string;
|
||||||
|
|
||||||
const [step, setStep] = useState(0);
|
const [step, setStep] = useState(0);
|
||||||
@@ -85,6 +87,11 @@ const SignupSSOPage = () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const SignupSSOPageQueryParamsSchema = z.object({
|
||||||
|
token: z.string()
|
||||||
|
});
|
||||||
|
|
||||||
export const Route = createFileRoute("/_restrict_login_signup/signup/sso/")({
|
export const Route = createFileRoute("/_restrict_login_signup/signup/sso/")({
|
||||||
component: SignupSSOPage
|
component: SignupSSOPage,
|
||||||
|
validateSearch: zodValidator(SignupSSOPageQueryParamsSchema)
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -7,9 +7,11 @@ import { Helmet } from "react-helmet";
|
|||||||
import { faWarning, faXmark } from "@fortawesome/free-solid-svg-icons";
|
import { faWarning, faXmark } from "@fortawesome/free-solid-svg-icons";
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
import { createFileRoute, Link, useNavigate, useSearch } from "@tanstack/react-router";
|
import { createFileRoute, Link, useNavigate, useSearch } from "@tanstack/react-router";
|
||||||
|
import { zodValidator } from "@tanstack/zod-adapter";
|
||||||
import jsrp from "jsrp";
|
import jsrp from "jsrp";
|
||||||
import nacl from "tweetnacl";
|
import nacl from "tweetnacl";
|
||||||
import { encodeBase64 } from "tweetnacl-util";
|
import { encodeBase64 } from "tweetnacl-util";
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
import { Mfa } from "@app/components/auth/Mfa";
|
import { Mfa } from "@app/components/auth/Mfa";
|
||||||
import InputField from "@app/components/basic/InputField";
|
import InputField from "@app/components/basic/InputField";
|
||||||
@@ -386,7 +388,7 @@ const SignupInvitePage = () => {
|
|||||||
setBackupKeyIssued
|
setBackupKeyIssued
|
||||||
});
|
});
|
||||||
navigate({
|
navigate({
|
||||||
to: `/organization/$organizationId/${ProjectType.SecretManager}` as const,
|
to: `/organization/$organizationId/${ProjectType.SecretManager}/overview` as const,
|
||||||
params: {
|
params: {
|
||||||
organizationId
|
organizationId
|
||||||
}
|
}
|
||||||
@@ -427,6 +429,13 @@ const SignupInvitePage = () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const SignupInvitePageQueryParamsSchema = z.object({
|
||||||
|
token: z.string(),
|
||||||
|
to: z.string(),
|
||||||
|
organization_id: z.string()
|
||||||
|
});
|
||||||
|
|
||||||
export const Route = createFileRoute("/_restrict_login_signup/signupinvite")({
|
export const Route = createFileRoute("/_restrict_login_signup/signupinvite")({
|
||||||
component: SignupInvitePage
|
component: SignupInvitePage,
|
||||||
|
validateSearch: zodValidator(SignupInvitePageQueryParamsSchema)
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user