mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-11 14:28:56 +00:00
Merge branch 'heads/main' into daniel/k8s-auth-gateway
This commit is contained in:
+9
@@ -58,6 +58,15 @@ export const OrgAdminProjects = withPermission(
|
||||
await orgAdminAccessProject.mutateAsync({
|
||||
projectId
|
||||
});
|
||||
if (type === ProjectType.CertificateManager) {
|
||||
await navigate({
|
||||
to: "/cert-manager/$projectId/subscribers" as const,
|
||||
params: {
|
||||
projectId
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
await navigate({
|
||||
to: `/${type}/$projectId/overview` as const,
|
||||
params: {
|
||||
|
||||
+5
@@ -22,6 +22,7 @@ import { HCVaultConnectionForm } from "./HCVaultConnectionForm";
|
||||
import { HumanitecConnectionForm } from "./HumanitecConnectionForm";
|
||||
import { LdapConnectionForm } from "./LdapConnectionForm";
|
||||
import { MsSqlConnectionForm } from "./MsSqlConnectionForm";
|
||||
import { OCIConnectionForm } from "./OCIConnectionForm";
|
||||
import { PostgresConnectionForm } from "./PostgresConnectionForm";
|
||||
import { TeamCityConnectionForm } from "./TeamCityConnectionForm";
|
||||
import { TerraformCloudConnectionForm } from "./TerraformCloudConnectionForm";
|
||||
@@ -101,6 +102,8 @@ const CreateForm = ({ app, onComplete }: CreateFormProps) => {
|
||||
return <LdapConnectionForm onSubmit={onSubmit} />;
|
||||
case AppConnection.TeamCity:
|
||||
return <TeamCityConnectionForm onSubmit={onSubmit} />;
|
||||
case AppConnection.OCI:
|
||||
return <OCIConnectionForm onSubmit={onSubmit} />;
|
||||
default:
|
||||
throw new Error(`Unhandled App ${app}`);
|
||||
}
|
||||
@@ -173,6 +176,8 @@ const UpdateForm = ({ appConnection, onComplete }: UpdateFormProps) => {
|
||||
return <LdapConnectionForm onSubmit={onSubmit} appConnection={appConnection} />;
|
||||
case AppConnection.TeamCity:
|
||||
return <TeamCityConnectionForm onSubmit={onSubmit} appConnection={appConnection} />;
|
||||
case AppConnection.OCI:
|
||||
return <OCIConnectionForm onSubmit={onSubmit} appConnection={appConnection} />;
|
||||
|
||||
default:
|
||||
throw new Error(`Unhandled App ${(appConnection as TAppConnection).app}`);
|
||||
|
||||
+215
@@ -0,0 +1,215 @@
|
||||
import { Controller, FormProvider, useForm } from "react-hook-form";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { z } from "zod";
|
||||
|
||||
import {
|
||||
Button,
|
||||
FormControl,
|
||||
Input,
|
||||
ModalClose,
|
||||
SecretInput,
|
||||
Select,
|
||||
SelectItem
|
||||
} from "@app/components/v2";
|
||||
import { APP_CONNECTION_MAP, getAppConnectionMethodDetails } from "@app/helpers/appConnections";
|
||||
import { OCIConnectionMethod, TOCIConnection } from "@app/hooks/api/appConnections";
|
||||
import { AppConnection } from "@app/hooks/api/appConnections/enums";
|
||||
|
||||
import {
|
||||
genericAppConnectionFieldsSchema,
|
||||
GenericAppConnectionsFields
|
||||
} from "./GenericAppConnectionFields";
|
||||
|
||||
type Props = {
|
||||
appConnection?: TOCIConnection;
|
||||
onSubmit: (formData: FormData) => void;
|
||||
};
|
||||
|
||||
const rootSchema = genericAppConnectionFieldsSchema.extend({
|
||||
app: z.literal(AppConnection.OCI)
|
||||
});
|
||||
|
||||
const formSchema = z.discriminatedUnion("method", [
|
||||
rootSchema.extend({
|
||||
method: z.literal(OCIConnectionMethod.AccessKey),
|
||||
credentials: z.object({
|
||||
userOcid: z
|
||||
.string()
|
||||
.trim()
|
||||
.min(1, "User OCID required")
|
||||
.regex(/^ocid1\.user\.oc1\.\..+$/, "Invalid User OCID format"),
|
||||
tenancyOcid: z
|
||||
.string()
|
||||
.trim()
|
||||
.min(1, "Tenancy OCID required")
|
||||
.regex(/^ocid1\.tenancy\.oc1\.\..+$/, "Invalid Tenancy OCID format"),
|
||||
region: z.string().trim().min(1, "Region required"),
|
||||
fingerprint: z.string().trim().min(1, "Fingerprint required"),
|
||||
privateKey: z.string().trim().min(1, "Private Key required")
|
||||
})
|
||||
})
|
||||
]);
|
||||
|
||||
type FormData = z.infer<typeof formSchema>;
|
||||
|
||||
export const OCIConnectionForm = ({ appConnection, onSubmit }: Props) => {
|
||||
const isUpdate = Boolean(appConnection);
|
||||
|
||||
const form = useForm<FormData>({
|
||||
resolver: zodResolver(formSchema),
|
||||
defaultValues: appConnection ?? {
|
||||
app: AppConnection.OCI,
|
||||
method: OCIConnectionMethod.AccessKey
|
||||
}
|
||||
});
|
||||
|
||||
const {
|
||||
handleSubmit,
|
||||
control,
|
||||
formState: { isSubmitting, isDirty }
|
||||
} = form;
|
||||
|
||||
return (
|
||||
<FormProvider {...form}>
|
||||
<form onSubmit={handleSubmit(onSubmit)}>
|
||||
{!isUpdate && <GenericAppConnectionsFields />}
|
||||
<Controller
|
||||
name="method"
|
||||
control={control}
|
||||
render={({ field: { value, onChange }, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
tooltipText={`The method you would like to use to connect with ${
|
||||
APP_CONNECTION_MAP[AppConnection.OCI].name
|
||||
}. This field cannot be changed after creation.`}
|
||||
errorText={error?.message}
|
||||
isError={Boolean(error?.message)}
|
||||
label="Method"
|
||||
>
|
||||
<Select
|
||||
isDisabled={isUpdate}
|
||||
value={value}
|
||||
onValueChange={(val) => onChange(val)}
|
||||
className="w-full border border-mineshaft-500"
|
||||
position="popper"
|
||||
dropdownContainerClassName="max-w-none"
|
||||
>
|
||||
{Object.values(OCIConnectionMethod).map((method) => {
|
||||
return (
|
||||
<SelectItem value={method} key={method}>
|
||||
{getAppConnectionMethodDetails(method).name}{" "}
|
||||
</SelectItem>
|
||||
);
|
||||
})}
|
||||
</Select>
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
<Controller
|
||||
name="credentials.userOcid"
|
||||
control={control}
|
||||
shouldUnregister
|
||||
render={({ field, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
errorText={error?.message}
|
||||
isError={Boolean(error?.message)}
|
||||
label="User OCID"
|
||||
tooltipClassName="max-w-sm"
|
||||
tooltipText="The unique identifier (OCID) associated with your OCI user account. You can find this in your OCI console under Identity > Domains > [domain] > Users > [user]."
|
||||
>
|
||||
<Input
|
||||
{...field}
|
||||
placeholder="ocid1.user.oc1..************************************************************"
|
||||
/>
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
<Controller
|
||||
name="credentials.tenancyOcid"
|
||||
control={control}
|
||||
shouldUnregister
|
||||
render={({ field, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
errorText={error?.message}
|
||||
isError={Boolean(error?.message)}
|
||||
label="Tenancy OCID"
|
||||
tooltipClassName="max-w-sm"
|
||||
tooltipText="The unique identifier (OCID) for your tenancy in Oracle Cloud. You can find this in your OCI console under Administration > Tenancy Details."
|
||||
>
|
||||
<Input
|
||||
{...field}
|
||||
placeholder="ocid1.tenancy.oc1..************************************************************"
|
||||
/>
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
<Controller
|
||||
name="credentials.region"
|
||||
control={control}
|
||||
shouldUnregister
|
||||
render={({ field, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
errorText={error?.message}
|
||||
isError={Boolean(error?.message)}
|
||||
label="Region"
|
||||
tooltipClassName="max-w-sm"
|
||||
tooltipText="The OCI region where your resources are located (e.g., us-ashburn-1, eu-frankfurt-1)."
|
||||
>
|
||||
<Input {...field} placeholder="us-ashburn-1" />
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
<Controller
|
||||
name="credentials.fingerprint"
|
||||
control={control}
|
||||
shouldUnregister
|
||||
render={({ field, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
errorText={error?.message}
|
||||
isError={Boolean(error?.message)}
|
||||
label="Fingerprint"
|
||||
tooltipClassName="max-w-sm"
|
||||
tooltipText="The fingerprint of the public key associated with your OCI API key. This is generated when you create an API key in your OCI user settings."
|
||||
>
|
||||
<Input {...field} placeholder="00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00" />
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
<Controller
|
||||
name="credentials.privateKey"
|
||||
control={control}
|
||||
shouldUnregister
|
||||
render={({ field: { value, onChange }, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
errorText={error?.message}
|
||||
isError={Boolean(error?.message)}
|
||||
label="Private Key PEM"
|
||||
>
|
||||
<SecretInput
|
||||
containerClassName="text-gray-400 group-focus-within:!border-primary-400/50 border border-mineshaft-500 bg-mineshaft-900 px-2.5 py-1.5"
|
||||
value={value}
|
||||
onChange={(e) => onChange(e.target.value)}
|
||||
/>
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
<div className="mt-8 flex items-center">
|
||||
<Button
|
||||
className="mr-4"
|
||||
size="sm"
|
||||
type="submit"
|
||||
colorSchema="secondary"
|
||||
isLoading={isSubmitting}
|
||||
isDisabled={isSubmitting || !isDirty}
|
||||
>
|
||||
{isUpdate ? "Update Credentials" : "Connect to OCI"}
|
||||
</Button>
|
||||
<ModalClose asChild>
|
||||
<Button colorSchema="secondary" variant="plain">
|
||||
Cancel
|
||||
</Button>
|
||||
</ModalClose>
|
||||
</div>
|
||||
</form>
|
||||
</FormProvider>
|
||||
);
|
||||
};
|
||||
@@ -1 +0,0 @@
|
||||
export { OrgAuthTab } from "./OrgAuthTab";
|
||||
+4
-7
@@ -4,8 +4,8 @@ import { createNotification } from "@app/components/notifications";
|
||||
import { Button, DeleteActionModal } from "@app/components/v2";
|
||||
import { useOrganization, useOrgPermission } from "@app/context";
|
||||
import { useDeleteOrgById } from "@app/hooks/api";
|
||||
import { clearSession } from "@app/hooks/api/users/queries";
|
||||
import { usePopUp } from "@app/hooks/usePopUp";
|
||||
import { navigateUserToOrg } from "@app/pages/auth/LoginPage/Login.utils";
|
||||
|
||||
export const OrgDeleteSection = () => {
|
||||
const navigate = useNavigate();
|
||||
@@ -13,9 +13,7 @@ export const OrgDeleteSection = () => {
|
||||
|
||||
const { membership } = useOrgPermission();
|
||||
|
||||
const { popUp, handlePopUpOpen, handlePopUpClose, handlePopUpToggle } = usePopUp([
|
||||
"deleteOrg"
|
||||
] as const);
|
||||
const { popUp, handlePopUpOpen, handlePopUpToggle } = usePopUp(["deleteOrg"] as const);
|
||||
|
||||
const { mutateAsync, isPending } = useDeleteOrgById();
|
||||
|
||||
@@ -32,9 +30,8 @@ export const OrgDeleteSection = () => {
|
||||
type: "success"
|
||||
});
|
||||
|
||||
await navigateUserToOrg(navigate);
|
||||
|
||||
handlePopUpClose("deleteOrg");
|
||||
clearSession();
|
||||
navigate({ to: "/login" });
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
createNotification({
|
||||
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
import { Link } from "@tanstack/react-router";
|
||||
|
||||
import { NoticeBannerV2 } from "@app/components/v2/NoticeBannerV2/NoticeBannerV2";
|
||||
import { OrgPermissionActions, OrgPermissionSubjects } from "@app/context";
|
||||
import { withPermission } from "@app/hoc";
|
||||
|
||||
import { OrgGenericAuthSection } from "./OrgGenericAuthSection";
|
||||
import { OrgUserAccessTokenLimitSection } from "./OrgUserAccessTokenLimitSection";
|
||||
|
||||
export const OrgSecurityTab = withPermission(
|
||||
() => {
|
||||
return (
|
||||
<>
|
||||
<NoticeBannerV2
|
||||
className="mx-auto mb-4"
|
||||
titleClassName="text-base"
|
||||
title="Single Sign-On (SSO) Settings"
|
||||
>
|
||||
<p className="mt-1 text-mineshaft-300">
|
||||
SSO Settings have been relocated:{" "}
|
||||
<Link
|
||||
className="text-mineshaft-200 underline underline-offset-2"
|
||||
to="/organization/sso"
|
||||
>
|
||||
Click here to view SSO Settings
|
||||
</Link>
|
||||
</p>
|
||||
</NoticeBannerV2>
|
||||
<OrgGenericAuthSection />
|
||||
<OrgUserAccessTokenLimitSection />
|
||||
</>
|
||||
);
|
||||
},
|
||||
{ action: OrgPermissionActions.Read, subject: OrgPermissionSubjects.Sso }
|
||||
);
|
||||
+3
-4
@@ -86,13 +86,12 @@ export const OrgUserAccessTokenLimitSection = () => {
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="mb-6 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
|
||||
<div className="mb-4 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
|
||||
<div className="flex w-full items-center justify-between">
|
||||
<p className="text-xl font-semibold">User Token Expiration</p>
|
||||
<p className="text-xl font-semibold">Session Length</p>
|
||||
</div>
|
||||
<p className="mb-4 mt-2 text-sm text-gray-400">
|
||||
This defines the maximum time a user token will be valid. After this time, the user will
|
||||
need to re-authenticate.
|
||||
Specify the duration of each login session for users in this organization.
|
||||
</p>
|
||||
<OrgPermissionCan I={OrgPermissionActions.Edit} a={OrgPermissionSubjects.Settings}>
|
||||
{(isAllowed) => (
|
||||
@@ -0,0 +1 @@
|
||||
export * from "./OrgSecurityTab";
|
||||
@@ -10,9 +10,9 @@ import { ProjectType } from "@app/hooks/api/workspace/types";
|
||||
import { AuditLogStreamsTab } from "../AuditLogStreamTab";
|
||||
import { ImportTab } from "../ImportTab";
|
||||
import { KmipTab } from "../KmipTab/OrgKmipTab";
|
||||
import { OrgAuthTab } from "../OrgAuthTab";
|
||||
import { OrgEncryptionTab } from "../OrgEncryptionTab";
|
||||
import { OrgGeneralTab } from "../OrgGeneralTab";
|
||||
import { OrgSecurityTab } from "../OrgSecurityTab";
|
||||
import { OrgWorkflowIntegrationTab } from "../OrgWorkflowIntegrationTab/OrgWorkflowIntegrationTab";
|
||||
|
||||
export const OrgTabGroup = () => {
|
||||
@@ -21,7 +21,7 @@ export const OrgTabGroup = () => {
|
||||
});
|
||||
const tabs = [
|
||||
{ name: "General", key: "tab-org-general", component: OrgGeneralTab },
|
||||
{ name: "Security", key: "tab-org-security", component: OrgAuthTab },
|
||||
{ name: "Security", key: "tab-org-security", component: OrgSecurityTab },
|
||||
{ name: "Encryption", key: "tab-org-encryption", component: OrgEncryptionTab },
|
||||
{
|
||||
name: "Workflow Integrations",
|
||||
|
||||
@@ -1,37 +1,33 @@
|
||||
import { faHome } from '@fortawesome/free-solid-svg-icons'
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
||||
import {
|
||||
createFileRoute,
|
||||
linkOptions,
|
||||
stripSearchParams,
|
||||
} from '@tanstack/react-router'
|
||||
import { zodValidator } from '@tanstack/zod-adapter'
|
||||
import { z } from 'zod'
|
||||
import { faHome } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { createFileRoute, linkOptions, stripSearchParams } from "@tanstack/react-router";
|
||||
import { zodValidator } from "@tanstack/zod-adapter";
|
||||
import { z } from "zod";
|
||||
|
||||
import { SettingsPage } from './SettingsPage'
|
||||
import { SettingsPage } from "./SettingsPage";
|
||||
|
||||
const SettingsPageQueryParams = z.object({
|
||||
selectedTab: z.string().catch(''),
|
||||
})
|
||||
selectedTab: z.string().catch("")
|
||||
});
|
||||
|
||||
export const Route = createFileRoute(
|
||||
'/_authenticate/_inject-org-details/_org-layout/organization/settings/',
|
||||
"/_authenticate/_inject-org-details/_org-layout/organization/settings/"
|
||||
)({
|
||||
component: SettingsPage,
|
||||
validateSearch: zodValidator(SettingsPageQueryParams),
|
||||
search: {
|
||||
middlewares: [stripSearchParams({ selectedTab: '' })],
|
||||
middlewares: [stripSearchParams({ selectedTab: "" })]
|
||||
},
|
||||
context: () => ({
|
||||
breadcrumbs: [
|
||||
{
|
||||
label: 'Home',
|
||||
label: "Home",
|
||||
icon: () => <FontAwesomeIcon icon={faHome} />,
|
||||
link: linkOptions({ to: '/' }),
|
||||
link: linkOptions({ to: "/" })
|
||||
},
|
||||
{
|
||||
label: 'Settings',
|
||||
},
|
||||
],
|
||||
}),
|
||||
})
|
||||
label: "Settings"
|
||||
}
|
||||
]
|
||||
})
|
||||
});
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
import { Helmet } from "react-helmet";
|
||||
|
||||
import { PageHeader } from "@app/components/v2";
|
||||
|
||||
import { SsoTabGroup } from "./components/SsoTabGroup";
|
||||
|
||||
export const SsoPage = () => {
|
||||
return (
|
||||
<>
|
||||
<Helmet>
|
||||
<title>Single Sign-On (SSO)</title>
|
||||
</Helmet>
|
||||
<div className="flex w-full justify-center bg-bunker-800 text-white">
|
||||
<div className="w-full max-w-7xl">
|
||||
<PageHeader title="Single Sign-On (SSO)" />
|
||||
<SsoTabGroup />
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
import { OrgPermissionActions, OrgPermissionSubjects } from "@app/context";
|
||||
import { withPermission } from "@app/hoc";
|
||||
|
||||
import { OrgGithubSyncSection } from "./OrgGithubSyncSection";
|
||||
import { OrgScimSection } from "./OrgSCIMSection";
|
||||
|
||||
export const OrgProvisioningTab = withPermission(
|
||||
() => {
|
||||
return (
|
||||
<>
|
||||
<OrgScimSection />
|
||||
<OrgGithubSyncSection />
|
||||
</>
|
||||
);
|
||||
},
|
||||
{ action: OrgPermissionActions.Read, subject: OrgPermissionSubjects.Sso }
|
||||
);
|
||||
@@ -0,0 +1 @@
|
||||
export * from "./OrgProvisioningTab";
|
||||
+1
-9
@@ -17,16 +17,12 @@ import { LoginMethod } from "@app/hooks/api/admin/types";
|
||||
import { LDAPModal } from "./LDAPModal";
|
||||
import { OIDCModal } from "./OIDCModal";
|
||||
import { OrgGeneralAuthSection } from "./OrgGeneralAuthSection";
|
||||
import { OrgGenericAuthSection } from "./OrgGenericAuthSection";
|
||||
import { OrgGithubSyncSection } from "./OrgGithubSyncSection";
|
||||
import { OrgLDAPSection } from "./OrgLDAPSection";
|
||||
import { OrgOIDCSection } from "./OrgOIDCSection";
|
||||
import { OrgScimSection } from "./OrgSCIMSection";
|
||||
import { OrgSSOSection } from "./OrgSSOSection";
|
||||
import { OrgUserAccessTokenLimitSection } from "./OrgUserAccessTokenLimitSection";
|
||||
import { SSOModal } from "./SSOModal";
|
||||
|
||||
export const OrgAuthTab = withPermission(
|
||||
export const OrgSsoTab = withPermission(
|
||||
() => {
|
||||
const {
|
||||
config: { enabledLoginMethods }
|
||||
@@ -167,8 +163,6 @@ export const OrgAuthTab = withPermission(
|
||||
|
||||
return (
|
||||
<>
|
||||
<OrgGenericAuthSection />
|
||||
<OrgUserAccessTokenLimitSection />
|
||||
{shouldShowCreateIdentityProviderView ? (
|
||||
createIdentityProviderView
|
||||
) : (
|
||||
@@ -183,8 +177,6 @@ export const OrgAuthTab = withPermission(
|
||||
{isLdapConfigured && shouldDisplaySection(LoginMethod.LDAP) && <OrgLDAPSection />}
|
||||
</>
|
||||
)}
|
||||
<OrgScimSection />
|
||||
<OrgGithubSyncSection />
|
||||
<UpgradePlanModal
|
||||
isOpen={popUp.upgradePlan.isOpen}
|
||||
onOpenChange={(isOpen) => handlePopUpToggle("upgradePlan", isOpen)}
|
||||
@@ -0,0 +1 @@
|
||||
export { OrgSsoTab } from "./OrgSsoTab";
|
||||
@@ -0,0 +1,37 @@
|
||||
import { useState } from "react";
|
||||
import { useSearch } from "@tanstack/react-router";
|
||||
|
||||
import { Tab, TabList, TabPanel, Tabs } from "@app/components/v2";
|
||||
import { ROUTE_PATHS } from "@app/const/routes";
|
||||
|
||||
import { OrgProvisioningTab } from "../OrgProvisioningTab";
|
||||
import { OrgSsoTab } from "../OrgSsoTab";
|
||||
|
||||
export const SsoTabGroup = () => {
|
||||
const search = useSearch({
|
||||
from: ROUTE_PATHS.Organization.SsoPage.id
|
||||
});
|
||||
const tabs = [
|
||||
{ name: "General", key: "tab-sso-auth", component: OrgSsoTab },
|
||||
{ name: "Provisioning", key: "tab-sso-identity", component: OrgProvisioningTab }
|
||||
];
|
||||
|
||||
const [selectedTab, setSelectedTab] = useState(search.selectedTab || tabs[0].key);
|
||||
|
||||
return (
|
||||
<Tabs value={selectedTab} onValueChange={setSelectedTab}>
|
||||
<TabList>
|
||||
{tabs.map((tab) => (
|
||||
<Tab value={tab.key} key={tab.key}>
|
||||
{tab.name}
|
||||
</Tab>
|
||||
))}
|
||||
</TabList>
|
||||
{tabs.map(({ key, component: Component }) => (
|
||||
<TabPanel value={key} key={`tab-panel-${key}`}>
|
||||
<Component />
|
||||
</TabPanel>
|
||||
))}
|
||||
</Tabs>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1 @@
|
||||
export { SsoTabGroup } from "./SsoTabGroup";
|
||||
@@ -0,0 +1,33 @@
|
||||
import { faHome } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { createFileRoute, linkOptions, stripSearchParams } from "@tanstack/react-router";
|
||||
import { zodValidator } from "@tanstack/zod-adapter";
|
||||
import { z } from "zod";
|
||||
|
||||
import { SsoPage } from "./SsoPage";
|
||||
|
||||
const SettingsPageQueryParams = z.object({
|
||||
selectedTab: z.string().catch("")
|
||||
});
|
||||
|
||||
export const Route = createFileRoute(
|
||||
"/_authenticate/_inject-org-details/_org-layout/organization/sso"
|
||||
)({
|
||||
component: SsoPage,
|
||||
validateSearch: zodValidator(SettingsPageQueryParams),
|
||||
search: {
|
||||
middlewares: [stripSearchParams({ selectedTab: "" })]
|
||||
},
|
||||
context: () => ({
|
||||
breadcrumbs: [
|
||||
{
|
||||
label: "Home",
|
||||
icon: () => <FontAwesomeIcon icon={faHome} />,
|
||||
link: linkOptions({ to: "/" })
|
||||
},
|
||||
{
|
||||
label: "Single Sign-On (SSO)"
|
||||
}
|
||||
]
|
||||
})
|
||||
});
|
||||
Reference in New Issue
Block a user