mirror of
https://github.com/fosrl/pangolin.git
synced 2026-03-04 17:56:38 +00:00
♻️ use react querty
This commit is contained in:
@@ -1,21 +1,22 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useEffect, useState } from "react";
|
import SetResourceHeaderAuthForm from "@app/components/SetResourceHeaderAuthForm";
|
||||||
import { ListRolesResponse } from "@server/routers/role";
|
import SetResourcePincodeForm from "@app/components/SetResourcePincodeForm";
|
||||||
import { toast } from "@app/hooks/useToast";
|
|
||||||
import { useOrgContext } from "@app/hooks/useOrgContext";
|
|
||||||
import { useResourceContext } from "@app/hooks/useResourceContext";
|
|
||||||
import { AxiosResponse } from "axios";
|
|
||||||
import { formatAxiosError } from "@app/lib/api";
|
|
||||||
import {
|
import {
|
||||||
GetResourceWhitelistResponse,
|
SettingsContainer,
|
||||||
ListResourceRolesResponse,
|
SettingsSection,
|
||||||
ListResourceUsersResponse
|
SettingsSectionBody,
|
||||||
} from "@server/routers/resource";
|
SettingsSectionDescription,
|
||||||
|
SettingsSectionFooter,
|
||||||
|
SettingsSectionForm,
|
||||||
|
SettingsSectionHeader,
|
||||||
|
SettingsSectionTitle
|
||||||
|
} from "@app/components/Settings";
|
||||||
|
import { SwitchInput } from "@app/components/SwitchInput";
|
||||||
|
import { Tag, TagInput } from "@app/components/tags/tag-input";
|
||||||
|
import { Alert, AlertDescription, AlertTitle } from "@app/components/ui/alert";
|
||||||
import { Button } from "@app/components/ui/button";
|
import { Button } from "@app/components/ui/button";
|
||||||
import { z } from "zod";
|
import { CheckboxWithLabel } from "@app/components/ui/checkbox";
|
||||||
import { useForm } from "react-hook-form";
|
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
|
||||||
import {
|
import {
|
||||||
Form,
|
Form,
|
||||||
FormControl,
|
FormControl,
|
||||||
@@ -25,32 +26,7 @@ import {
|
|||||||
FormLabel,
|
FormLabel,
|
||||||
FormMessage
|
FormMessage
|
||||||
} from "@app/components/ui/form";
|
} from "@app/components/ui/form";
|
||||||
import { ListUsersResponse } from "@server/routers/user";
|
|
||||||
import { Binary, Key, Bot } from "lucide-react";
|
|
||||||
import SetResourcePasswordForm from "components/SetResourcePasswordForm";
|
|
||||||
import SetResourcePincodeForm from "@app/components/SetResourcePincodeForm";
|
|
||||||
import SetResourceHeaderAuthForm from "@app/components/SetResourceHeaderAuthForm";
|
|
||||||
import { createApiClient } from "@app/lib/api";
|
|
||||||
import { useEnvContext } from "@app/hooks/useEnvContext";
|
|
||||||
import {
|
|
||||||
SettingsContainer,
|
|
||||||
SettingsSection,
|
|
||||||
SettingsSectionTitle,
|
|
||||||
SettingsSectionHeader,
|
|
||||||
SettingsSectionDescription,
|
|
||||||
SettingsSectionBody,
|
|
||||||
SettingsSectionFooter,
|
|
||||||
SettingsSectionForm
|
|
||||||
} from "@app/components/Settings";
|
|
||||||
import { SwitchInput } from "@app/components/SwitchInput";
|
|
||||||
import { InfoPopup } from "@app/components/ui/info-popup";
|
import { InfoPopup } from "@app/components/ui/info-popup";
|
||||||
import { Tag, TagInput } from "@app/components/tags/tag-input";
|
|
||||||
import { useRouter } from "next/navigation";
|
|
||||||
import { UserType } from "@server/types/UserTypes";
|
|
||||||
import { Alert, AlertDescription, AlertTitle } from "@app/components/ui/alert";
|
|
||||||
import { InfoIcon } from "lucide-react";
|
|
||||||
import { useTranslations } from "next-intl";
|
|
||||||
import { CheckboxWithLabel } from "@app/components/ui/checkbox";
|
|
||||||
import {
|
import {
|
||||||
Select,
|
Select,
|
||||||
SelectContent,
|
SelectContent,
|
||||||
@@ -58,10 +34,33 @@ import {
|
|||||||
SelectTrigger,
|
SelectTrigger,
|
||||||
SelectValue
|
SelectValue
|
||||||
} from "@app/components/ui/select";
|
} from "@app/components/ui/select";
|
||||||
import { Separator } from "@app/components/ui/separator";
|
import { useEnvContext } from "@app/hooks/useEnvContext";
|
||||||
import { build } from "@server/build";
|
import { useOrgContext } from "@app/hooks/useOrgContext";
|
||||||
|
import { useResourceContext } from "@app/hooks/useResourceContext";
|
||||||
import { useSubscriptionStatusContext } from "@app/hooks/useSubscriptionStatusContext";
|
import { useSubscriptionStatusContext } from "@app/hooks/useSubscriptionStatusContext";
|
||||||
import { TierId } from "@server/lib/billing/tiers";
|
import { toast } from "@app/hooks/useToast";
|
||||||
|
import { createApiClient, formatAxiosError } from "@app/lib/api";
|
||||||
|
import { orgQueries, resourceQueries } from "@app/lib/queries";
|
||||||
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
|
import { build } from "@server/build";
|
||||||
|
import {
|
||||||
|
GetResourceWhitelistResponse,
|
||||||
|
ListResourceRolesResponse,
|
||||||
|
ListResourceUsersResponse
|
||||||
|
} from "@server/routers/resource";
|
||||||
|
import { ListRolesResponse } from "@server/routers/role";
|
||||||
|
import { ListUsersResponse } from "@server/routers/user";
|
||||||
|
import { UserType } from "@server/types/UserTypes";
|
||||||
|
import { useQuery, useQueryClient } from "@tanstack/react-query";
|
||||||
|
import { AxiosResponse } from "axios";
|
||||||
|
import SetResourcePasswordForm from "components/SetResourcePasswordForm";
|
||||||
|
import type { text } from "express";
|
||||||
|
import { Binary, Bot, InfoIcon, Key } from "lucide-react";
|
||||||
|
import { useTranslations } from "next-intl";
|
||||||
|
import { useRouter } from "next/navigation";
|
||||||
|
import { useEffect, useMemo, useRef, useState, useTransition } from "react";
|
||||||
|
import { useForm } from "react-hook-form";
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
const UsersRolesFormSchema = z.object({
|
const UsersRolesFormSchema = z.object({
|
||||||
roles: z.array(
|
roles: z.array(
|
||||||
@@ -100,14 +99,83 @@ export default function ResourceAuthenticationPage() {
|
|||||||
|
|
||||||
const subscription = useSubscriptionStatusContext();
|
const subscription = useSubscriptionStatusContext();
|
||||||
|
|
||||||
const [pageLoading, setPageLoading] = useState(true);
|
const queryClient = useQueryClient();
|
||||||
|
const { data: resourceRoles = [], isLoading: isLoadingResourceRoles } =
|
||||||
|
useQuery(
|
||||||
|
resourceQueries.resourceRoles({
|
||||||
|
resourceId: resource.resourceId
|
||||||
|
})
|
||||||
|
);
|
||||||
|
const { data: resourceUsers = [], isLoading: isLoadingResourceUsers } =
|
||||||
|
useQuery(
|
||||||
|
resourceQueries.resourceUsers({
|
||||||
|
resourceId: resource.resourceId
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
const [allRoles, setAllRoles] = useState<{ id: string; text: string }[]>(
|
const { data: whitelist = [], isLoading: isLoadingWhiteList } = useQuery(
|
||||||
[]
|
resourceQueries.resourceWhitelist({
|
||||||
|
resourceId: resource.resourceId
|
||||||
|
})
|
||||||
);
|
);
|
||||||
const [allUsers, setAllUsers] = useState<{ id: string; text: string }[]>(
|
|
||||||
[]
|
const { data: orgRoles = [], isLoading: isLoadingOrgRoles } = useQuery(
|
||||||
|
orgQueries.roles({
|
||||||
|
orgId: org.org.orgId
|
||||||
|
})
|
||||||
);
|
);
|
||||||
|
const { data: orgUsers = [], isLoading: isLoadingOrgUsers } = useQuery(
|
||||||
|
orgQueries.users({
|
||||||
|
orgId: org.org.orgId
|
||||||
|
})
|
||||||
|
);
|
||||||
|
const { data: orgIdps = [], isLoading: isLoadingOrgIdps } = useQuery(
|
||||||
|
orgQueries.identityProviders({
|
||||||
|
orgId: org.org.orgId
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
const pageLoading =
|
||||||
|
isLoadingOrgRoles ||
|
||||||
|
isLoadingOrgUsers ||
|
||||||
|
isLoadingResourceRoles ||
|
||||||
|
isLoadingResourceUsers ||
|
||||||
|
isLoadingWhiteList ||
|
||||||
|
isLoadingOrgIdps;
|
||||||
|
|
||||||
|
const allRoles = useMemo(() => {
|
||||||
|
return orgRoles
|
||||||
|
.map((role) => ({
|
||||||
|
id: role.roleId.toString(),
|
||||||
|
text: role.name
|
||||||
|
}))
|
||||||
|
.filter((role) => role.text !== "Admin");
|
||||||
|
}, [orgRoles]);
|
||||||
|
|
||||||
|
const allUsers = useMemo(() => {
|
||||||
|
return orgUsers.map((user) => ({
|
||||||
|
id: user.id.toString(),
|
||||||
|
text: `${user.email || user.username}${user.type !== UserType.Internal ? ` (${user.idpName})` : ""}`
|
||||||
|
}));
|
||||||
|
}, [orgUsers]);
|
||||||
|
|
||||||
|
const allIdps = useMemo(() => {
|
||||||
|
if (build === "saas") {
|
||||||
|
if (subscription?.subscribed) {
|
||||||
|
return orgIdps.map((idp) => ({
|
||||||
|
id: idp.idpId,
|
||||||
|
text: idp.name
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return orgIdps.map((idp) => ({
|
||||||
|
id: idp.idpId,
|
||||||
|
text: idp.name
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
}, [orgIdps]);
|
||||||
|
|
||||||
const [activeRolesTagIndex, setActiveRolesTagIndex] = useState<
|
const [activeRolesTagIndex, setActiveRolesTagIndex] = useState<
|
||||||
number | null
|
number | null
|
||||||
>(null);
|
>(null);
|
||||||
@@ -131,10 +199,10 @@ export default function ResourceAuthenticationPage() {
|
|||||||
const [selectedIdpId, setSelectedIdpId] = useState<number | null>(
|
const [selectedIdpId, setSelectedIdpId] = useState<number | null>(
|
||||||
resource.skipToIdpId || null
|
resource.skipToIdpId || null
|
||||||
);
|
);
|
||||||
const [allIdps, setAllIdps] = useState<{ id: number; text: string }[]>([]);
|
|
||||||
|
|
||||||
const [loadingSaveUsersRoles, setLoadingSaveUsersRoles] = useState(false);
|
const [loadingSaveUsersRoles, setLoadingSaveUsersRoles] = useState(false);
|
||||||
const [loadingSaveWhitelist, setLoadingSaveWhitelist] = useState(false);
|
const [loadingSaveWhitelist, startSaveWhitelistTransition] =
|
||||||
|
useTransition();
|
||||||
|
|
||||||
const [loadingRemoveResourcePassword, setLoadingRemoveResourcePassword] =
|
const [loadingRemoveResourcePassword, setLoadingRemoveResourcePassword] =
|
||||||
useState(false);
|
useState(false);
|
||||||
@@ -159,126 +227,35 @@ export default function ResourceAuthenticationPage() {
|
|||||||
defaultValues: { emails: [] }
|
defaultValues: { emails: [] }
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const hasInitializedRef = useRef(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchData = async () => {
|
if (!pageLoading || hasInitializedRef.current) return;
|
||||||
try {
|
|
||||||
const [
|
|
||||||
rolesResponse,
|
|
||||||
resourceRolesResponse,
|
|
||||||
usersResponse,
|
|
||||||
resourceUsersResponse,
|
|
||||||
whitelist,
|
|
||||||
idpsResponse
|
|
||||||
] = await Promise.all([
|
|
||||||
api.get<AxiosResponse<ListRolesResponse>>(
|
|
||||||
`/org/${org?.org.orgId}/roles`
|
|
||||||
),
|
|
||||||
api.get<AxiosResponse<ListResourceRolesResponse>>(
|
|
||||||
`/resource/${resource.resourceId}/roles`
|
|
||||||
),
|
|
||||||
api.get<AxiosResponse<ListUsersResponse>>(
|
|
||||||
`/org/${org?.org.orgId}/users`
|
|
||||||
),
|
|
||||||
api.get<AxiosResponse<ListResourceUsersResponse>>(
|
|
||||||
`/resource/${resource.resourceId}/users`
|
|
||||||
),
|
|
||||||
api.get<AxiosResponse<GetResourceWhitelistResponse>>(
|
|
||||||
`/resource/${resource.resourceId}/whitelist`
|
|
||||||
),
|
|
||||||
api.get<
|
|
||||||
AxiosResponse<{
|
|
||||||
idps: { idpId: number; name: string }[];
|
|
||||||
}>
|
|
||||||
>(build === "saas" ? `/org/${org?.org.orgId}/idp` : "/idp")
|
|
||||||
]);
|
|
||||||
|
|
||||||
setAllRoles(
|
usersRolesForm.setValue("roles", allRoles);
|
||||||
rolesResponse.data.data.roles
|
usersRolesForm.setValue("users", allUsers);
|
||||||
.map((role) => ({
|
whitelistForm.setValue(
|
||||||
id: role.roleId.toString(),
|
"emails",
|
||||||
text: role.name
|
whitelist.map((w) => ({
|
||||||
}))
|
id: w.email,
|
||||||
.filter((role) => role.text !== "Admin")
|
text: w.email
|
||||||
);
|
}))
|
||||||
|
);
|
||||||
usersRolesForm.setValue(
|
if (autoLoginEnabled && !selectedIdpId && orgIdps.length > 0) {
|
||||||
"roles",
|
setSelectedIdpId(orgIdps[0].idpId);
|
||||||
resourceRolesResponse.data.data.roles
|
}
|
||||||
.map((i) => ({
|
hasInitializedRef.current = true;
|
||||||
id: i.roleId.toString(),
|
}, [
|
||||||
text: i.name
|
pageLoading,
|
||||||
}))
|
allRoles,
|
||||||
.filter((role) => role.text !== "Admin")
|
allUsers,
|
||||||
);
|
whitelist,
|
||||||
|
autoLoginEnabled,
|
||||||
setAllUsers(
|
selectedIdpId,
|
||||||
usersResponse.data.data.users.map((user) => ({
|
orgIdps
|
||||||
id: user.id.toString(),
|
]);
|
||||||
text: `${user.email || user.username}${user.type !== UserType.Internal ? ` (${user.idpName})` : ""}`
|
|
||||||
}))
|
|
||||||
);
|
|
||||||
|
|
||||||
usersRolesForm.setValue(
|
|
||||||
"users",
|
|
||||||
resourceUsersResponse.data.data.users.map((i) => ({
|
|
||||||
id: i.userId.toString(),
|
|
||||||
text: `${i.email || i.username}${i.type !== UserType.Internal ? ` (${i.idpName})` : ""}`
|
|
||||||
}))
|
|
||||||
);
|
|
||||||
|
|
||||||
whitelistForm.setValue(
|
|
||||||
"emails",
|
|
||||||
whitelist.data.data.whitelist.map((w) => ({
|
|
||||||
id: w.email,
|
|
||||||
text: w.email
|
|
||||||
}))
|
|
||||||
);
|
|
||||||
|
|
||||||
if (build === "saas") {
|
|
||||||
if (subscription?.subscribed) {
|
|
||||||
setAllIdps(
|
|
||||||
idpsResponse.data.data.idps.map((idp) => ({
|
|
||||||
id: idp.idpId,
|
|
||||||
text: idp.name
|
|
||||||
}))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
setAllIdps(
|
|
||||||
idpsResponse.data.data.idps.map((idp) => ({
|
|
||||||
id: idp.idpId,
|
|
||||||
text: idp.name
|
|
||||||
}))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
autoLoginEnabled &&
|
|
||||||
!selectedIdpId &&
|
|
||||||
idpsResponse.data.data.idps.length > 0
|
|
||||||
) {
|
|
||||||
setSelectedIdpId(idpsResponse.data.data.idps[0].idpId);
|
|
||||||
}
|
|
||||||
|
|
||||||
setPageLoading(false);
|
|
||||||
} catch (e) {
|
|
||||||
console.error(e);
|
|
||||||
toast({
|
|
||||||
variant: "destructive",
|
|
||||||
title: t("resourceErrorAuthFetch"),
|
|
||||||
description: formatAxiosError(
|
|
||||||
e,
|
|
||||||
t("resourceErrorAuthFetchDescription")
|
|
||||||
)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
fetchData();
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
async function saveWhitelist() {
|
async function saveWhitelist() {
|
||||||
setLoadingSaveWhitelist(true);
|
|
||||||
try {
|
try {
|
||||||
await api.post(`/resource/${resource.resourceId}`, {
|
await api.post(`/resource/${resource.resourceId}`, {
|
||||||
emailWhitelistEnabled: whitelistEnabled
|
emailWhitelistEnabled: whitelistEnabled
|
||||||
@@ -299,6 +276,11 @@ export default function ResourceAuthenticationPage() {
|
|||||||
description: t("resourceWhitelistSaveDescription")
|
description: t("resourceWhitelistSaveDescription")
|
||||||
});
|
});
|
||||||
router.refresh();
|
router.refresh();
|
||||||
|
await queryClient.invalidateQueries(
|
||||||
|
resourceQueries.resourceWhitelist({
|
||||||
|
resourceId: resource.resourceId
|
||||||
|
})
|
||||||
|
);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
toast({
|
toast({
|
||||||
@@ -309,8 +291,6 @@ export default function ResourceAuthenticationPage() {
|
|||||||
t("resourceErrorWhitelistSaveDescription")
|
t("resourceErrorWhitelistSaveDescription")
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
} finally {
|
|
||||||
setLoadingSaveWhitelist(false);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -984,7 +964,9 @@ export default function ResourceAuthenticationPage() {
|
|||||||
</SettingsSectionBody>
|
</SettingsSectionBody>
|
||||||
<SettingsSectionFooter>
|
<SettingsSectionFooter>
|
||||||
<Button
|
<Button
|
||||||
onClick={saveWhitelist}
|
onClick={() =>
|
||||||
|
startSaveWhitelistTransition(saveWhitelist)
|
||||||
|
}
|
||||||
form="whitelist-form"
|
form="whitelist-form"
|
||||||
loading={loadingSaveWhitelist}
|
loading={loadingSaveWhitelist}
|
||||||
disabled={loadingSaveWhitelist}
|
disabled={loadingSaveWhitelist}
|
||||||
|
|||||||
@@ -15,7 +15,10 @@ import z from "zod";
|
|||||||
import { remote } from "./api";
|
import { remote } from "./api";
|
||||||
import { durationToMs } from "./durationToMs";
|
import { durationToMs } from "./durationToMs";
|
||||||
import type { QueryRequestAnalyticsResponse } from "@server/routers/auditLogs";
|
import type { QueryRequestAnalyticsResponse } from "@server/routers/auditLogs";
|
||||||
import type { ListResourceNamesResponse } from "@server/routers/resource";
|
import type {
|
||||||
|
GetResourceWhitelistResponse,
|
||||||
|
ListResourceNamesResponse
|
||||||
|
} from "@server/routers/resource";
|
||||||
import type { ListTargetsResponse } from "@server/routers/target";
|
import type { ListTargetsResponse } from "@server/routers/target";
|
||||||
import type { ListDomainsResponse } from "@server/routers/domain";
|
import type { ListDomainsResponse } from "@server/routers/domain";
|
||||||
|
|
||||||
@@ -152,6 +155,18 @@ export const orgQueries = {
|
|||||||
>(`/org/${orgId}/domains`, { signal });
|
>(`/org/${orgId}/domains`, { signal });
|
||||||
return res.data.data.domains;
|
return res.data.data.domains;
|
||||||
}
|
}
|
||||||
|
}),
|
||||||
|
identityProviders: ({ orgId }: { orgId: string }) =>
|
||||||
|
queryOptions({
|
||||||
|
queryKey: ["ORG", orgId, "IDPS"] as const,
|
||||||
|
queryFn: async ({ signal, meta }) => {
|
||||||
|
const res = await meta!.api.get<
|
||||||
|
AxiosResponse<{
|
||||||
|
idps: { idpId: number; name: string }[];
|
||||||
|
}>
|
||||||
|
>(build === "saas" ? `/org/${orgId}/idp` : "/idp", { signal });
|
||||||
|
return res.data.data.idps;
|
||||||
|
}
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -250,6 +265,17 @@ export const resourceQueries = {
|
|||||||
return res.data.data.targets;
|
return res.data.data.targets;
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
|
resourceWhitelist: ({ resourceId }: { resourceId: number }) =>
|
||||||
|
queryOptions({
|
||||||
|
queryKey: ["RESOURCES", resourceId, "WHITELISTS"] as const,
|
||||||
|
queryFn: async ({ signal, meta }) => {
|
||||||
|
const res = await meta!.api.get<
|
||||||
|
AxiosResponse<GetResourceWhitelistResponse>
|
||||||
|
>(`/resource/${resourceId}/whitelist`, { signal });
|
||||||
|
|
||||||
|
return res.data.data.whitelist;
|
||||||
|
}
|
||||||
|
}),
|
||||||
listNamesPerOrg: (orgId: string) =>
|
listNamesPerOrg: (orgId: string) =>
|
||||||
queryOptions({
|
queryOptions({
|
||||||
queryKey: ["RESOURCES_NAMES", orgId] as const,
|
queryKey: ["RESOURCES_NAMES", orgId] as const,
|
||||||
|
|||||||
Reference in New Issue
Block a user