mirror of
https://github.com/fosrl/pangolin.git
synced 2026-05-06 16:38:48 +00:00
🚧 wip: update resource policy form
This commit is contained in:
@@ -642,7 +642,11 @@
|
||||
"policyErrorCreate": "Error creating policy",
|
||||
"policyErrorCreateDescription": "An error occurred when creating the policy",
|
||||
"policyErrorCreateMessageDescription": "An unexpected error occurred",
|
||||
"policyErrorUpdate": "Error updating policy",
|
||||
"policyErrorUpdateDescription": "An error occurred when updating the policy",
|
||||
"policyErrorUpdateMessageDescription": "An unexpected error occurred",
|
||||
"policyCreatedSuccess": "Resource policy succesfully created",
|
||||
"policyUpdatedSuccess": "Resource policy succesfully updated",
|
||||
"resourceErrorCreate": "Error creating resource",
|
||||
"resourceErrorCreateDescription": "An error occurred when creating the resource",
|
||||
"resourceErrorCreateMessage": "Error creating resource:",
|
||||
|
||||
@@ -638,6 +638,13 @@ authenticated.get(
|
||||
policy.getResourcePolicy
|
||||
);
|
||||
|
||||
authenticated.put(
|
||||
"/resource-policy/:resourcePolicyId",
|
||||
verifyResourcePolicyAccess,
|
||||
verifyUserHasAction(ActionsEnum.updateResourcePolicy),
|
||||
policy.updateResourcePolicy
|
||||
);
|
||||
|
||||
// authenticated.get(
|
||||
// "/role/:roleId",
|
||||
// verifyRoleAccess,
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
export * from "./getResourcePolicy";
|
||||
export * from "./updateResourcePolicy";
|
||||
|
||||
@@ -4,14 +4,7 @@ import { OpenAPITags, registry } from "@server/openApi";
|
||||
import HttpCode from "@server/types/HttpCode";
|
||||
import createHttpError from "http-errors";
|
||||
import { fromError } from "zod-validation-error";
|
||||
import {
|
||||
db,
|
||||
orgs,
|
||||
resourcePolicies,
|
||||
rolePolicies,
|
||||
userPolicies,
|
||||
type ResourcePolicy
|
||||
} from "@server/db";
|
||||
import { db, orgs, resourcePolicies, type ResourcePolicy } from "@server/db";
|
||||
import { and, eq } from "drizzle-orm";
|
||||
import logger from "@server/logger";
|
||||
import response from "@server/lib/response";
|
||||
@@ -3,7 +3,7 @@ import SettingsSectionTitle from "@app/components/SettingsSectionTitle";
|
||||
import { Button } from "@app/components/ui/button";
|
||||
import { internal } from "@app/lib/api";
|
||||
import { authCookieHeader } from "@app/lib/api/cookies";
|
||||
import type { ResourcePolicy } from "@server/db";
|
||||
import { ResourcePolicyProvider } from "@app/providers/ResourcePolicyProvider";
|
||||
import type { GetResourcePolicyResponse } from "@server/routers/policy";
|
||||
import type { AxiosResponse } from "axios";
|
||||
import { getTranslations } from "next-intl/server";
|
||||
@@ -18,7 +18,7 @@ export default async function EditPolicyPage(props: EditPolicyPageProps) {
|
||||
const params = await props.params;
|
||||
const t = await getTranslations();
|
||||
|
||||
let policy: ResourcePolicy | null = null;
|
||||
let policyResponse: GetResourcePolicyResponse | null = null;
|
||||
try {
|
||||
const res = await internal.get<
|
||||
AxiosResponse<GetResourcePolicyResponse>
|
||||
@@ -26,12 +26,12 @@ export default async function EditPolicyPage(props: EditPolicyPageProps) {
|
||||
`/org/${params.orgId}/resource-policy/${params.niceId}`,
|
||||
await authCookieHeader()
|
||||
);
|
||||
policy = res.data.data.policy;
|
||||
policyResponse = res.data.data;
|
||||
} catch {
|
||||
redirect(`/${params.orgId}/settings/policies/resource`);
|
||||
}
|
||||
|
||||
if (!policy) {
|
||||
if (!policyResponse) {
|
||||
redirect(`/${params.orgId}/settings/policies/resource`);
|
||||
}
|
||||
|
||||
@@ -40,7 +40,7 @@ export default async function EditPolicyPage(props: EditPolicyPageProps) {
|
||||
<div className="flex justify-between">
|
||||
<SettingsSectionTitle
|
||||
title={t("resourcePolicySetting", {
|
||||
policyName: policy.name
|
||||
policyName: policyResponse.policy.name
|
||||
})}
|
||||
description={t("resourcePolicySettingDescription")}
|
||||
/>
|
||||
@@ -52,7 +52,9 @@ export default async function EditPolicyPage(props: EditPolicyPageProps) {
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<EditPolicyForm policy={policy} />
|
||||
<ResourcePolicyProvider policy={policyResponse}>
|
||||
<EditPolicyForm />
|
||||
</ResourcePolicyProvider>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -204,14 +204,10 @@ export function CreatePolicyForm({}: CreatePolicyFormProps) {
|
||||
});
|
||||
|
||||
if (res && res.status === 201) {
|
||||
const id = res.data.data.resourcePolicyId;
|
||||
const niceId = res.data.data.niceId;
|
||||
|
||||
router.push(`/${org.org.orgId}/settings/policies/resources/`);
|
||||
// should redirect to the details page
|
||||
// router.push(
|
||||
// `/${org.org.orgId}/settings/policies/resources/${niceId}`
|
||||
// );
|
||||
router.push(
|
||||
`/${org.org.orgId}/settings/policies/resource/${niceId}`
|
||||
);
|
||||
toast({
|
||||
title: t("success"),
|
||||
description: t("policyCreatedSuccess")
|
||||
|
||||
@@ -121,23 +121,21 @@ import {
|
||||
|
||||
import { useCallback, useMemo, useState, useActionState } from "react";
|
||||
import { UseFormReturn, useForm, useWatch } from "react-hook-form";
|
||||
import router from "next/navigation";
|
||||
import { useResourcePolicyContext } from "@app/providers/ResourcePolicyProvider";
|
||||
|
||||
// ─── EditPolicyForm ─────────────────────────────────────────────────────────
|
||||
|
||||
export type EditPolicyFormProps = {
|
||||
policy: ResourcePolicy;
|
||||
hidePolicyNameForm?: boolean;
|
||||
};
|
||||
|
||||
export function EditPolicyForm({
|
||||
hidePolicyNameForm,
|
||||
policy
|
||||
}: EditPolicyFormProps) {
|
||||
export function EditPolicyForm({ hidePolicyNameForm }: EditPolicyFormProps) {
|
||||
const { org } = useOrgContext();
|
||||
const t = useTranslations();
|
||||
const { env } = useEnvContext();
|
||||
const api = createApiClient({ env });
|
||||
const [, formAction, isSubmitting] = useActionState(onSubmit, null);
|
||||
// const [, formAction, isSubmitting] = useActionState(onSubmit, null);
|
||||
const { isPaidUser } = usePaidStatus();
|
||||
|
||||
const router = useRouter();
|
||||
@@ -145,7 +143,7 @@ export function EditPolicyForm({
|
||||
const isMaxmindAvailable = !!(
|
||||
env.server.maxmind_db_path && env.server.maxmind_db_path.length > 0
|
||||
);
|
||||
const isMaxmindAsnAvailable = !!(
|
||||
const isMaxmindASNAvailable = !!(
|
||||
env.server.maxmind_asn_path && env.server.maxmind_asn_path.length > 0
|
||||
);
|
||||
|
||||
@@ -162,75 +160,76 @@ export function EditPolicyForm({
|
||||
})
|
||||
);
|
||||
|
||||
const form = useForm<PolicyFormValues>({
|
||||
resolver: zodResolver(createPolicySchema) as any,
|
||||
defaultValues: {
|
||||
name: policy.name,
|
||||
sso: true,
|
||||
skipToIdpId: null,
|
||||
emailWhitelistEnabled: false,
|
||||
roles: [],
|
||||
users: [],
|
||||
emails: [],
|
||||
applyRules: false,
|
||||
rules: [],
|
||||
password: null,
|
||||
headerAuth: null,
|
||||
pincode: null
|
||||
}
|
||||
});
|
||||
// const form = useForm<PolicyFormValues>({
|
||||
// resolver: zodResolver(createPolicySchema) as any,
|
||||
// defaultValues: {
|
||||
// name: "",
|
||||
// sso: true,
|
||||
// skipToIdpId: null,
|
||||
// emailWhitelistEnabled: false,
|
||||
// roles: [],
|
||||
// users: [],
|
||||
// emails: [],
|
||||
// applyRules: false,
|
||||
// rules: [],
|
||||
// password: null,
|
||||
// headerAuth: null,
|
||||
// pincode: null
|
||||
// }
|
||||
// });
|
||||
|
||||
async function onSubmit() {
|
||||
const isValid = await form.trigger();
|
||||
// async function onSubmit() {
|
||||
// return;
|
||||
// // const isValid = await form.trigger();
|
||||
|
||||
if (!isValid) return;
|
||||
// // if (!isValid) return;
|
||||
|
||||
const payload = form.getValues();
|
||||
// // const payload = form.getValues();
|
||||
|
||||
try {
|
||||
const res = await api
|
||||
.post<AxiosResponse<ResourcePolicy>>(
|
||||
`/org/${org.org.orgId}/resource-policy/`,
|
||||
{
|
||||
name: payload.name,
|
||||
sso: payload.sso,
|
||||
roleIds: payload.roles.map((r) => r.id),
|
||||
userIds: payload.users.map((u) => u.id)
|
||||
}
|
||||
)
|
||||
.catch((e) => {
|
||||
toast({
|
||||
variant: "destructive",
|
||||
title: t("policyErrorCreate"),
|
||||
description: formatAxiosError(
|
||||
e,
|
||||
t("policyErrorCreateDescription")
|
||||
)
|
||||
});
|
||||
});
|
||||
// // try {
|
||||
// // const res = await api
|
||||
// // .post<AxiosResponse<ResourcePolicy>>(
|
||||
// // `/org/${org.org.orgId}/resource-policy/`,
|
||||
// // {
|
||||
// // name: payload.name,
|
||||
// // sso: payload.sso,
|
||||
// // roleIds: payload.roles.map((r) => r.id),
|
||||
// // userIds: payload.users.map((u) => u.id)
|
||||
// // }
|
||||
// // )
|
||||
// // .catch((e) => {
|
||||
// // toast({
|
||||
// // variant: "destructive",
|
||||
// // title: t("policyErrorCreate"),
|
||||
// // description: formatAxiosError(
|
||||
// // e,
|
||||
// // t("policyErrorCreateDescription")
|
||||
// // )
|
||||
// // });
|
||||
// // });
|
||||
|
||||
if (res && res.status === 201) {
|
||||
const id = res.data.data.resourcePolicyId;
|
||||
const niceId = res.data.data.niceId;
|
||||
// // if (res && res.status === 201) {
|
||||
// // const id = res.data.data.resourcePolicyId;
|
||||
// // const niceId = res.data.data.niceId;
|
||||
|
||||
router.push(`/${org.org.orgId}/settings/policies/resources/`);
|
||||
// should redirect to the details page
|
||||
// router.push(
|
||||
// `/${org.org.orgId}/settings/policies/resources/${niceId}`
|
||||
// );
|
||||
toast({
|
||||
title: t("success"),
|
||||
description: t("policyCreatedSuccess")
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
toast({
|
||||
variant: "destructive",
|
||||
title: t("policyErrorCreate"),
|
||||
description: t("policyErrorCreateMessageDescription")
|
||||
});
|
||||
}
|
||||
}
|
||||
// // router.push(`/${org.org.orgId}/settings/policies/resources/`);
|
||||
// // // should redirect to the details page
|
||||
// // // router.push(
|
||||
// // // `/${org.org.orgId}/settings/policies/resources/${niceId}`
|
||||
// // // );
|
||||
// // toast({
|
||||
// // title: t("success"),
|
||||
// // description: t("policyCreatedSuccess")
|
||||
// // });
|
||||
// // }
|
||||
// // } catch (e) {
|
||||
// // toast({
|
||||
// // variant: "destructive",
|
||||
// // title: t("policyErrorCreate"),
|
||||
// // description: t("policyErrorCreateMessageDescription")
|
||||
// // });
|
||||
// // }
|
||||
// }
|
||||
|
||||
const allRoles = useMemo(
|
||||
() =>
|
||||
@@ -271,12 +270,12 @@ export function EditPolicyForm({
|
||||
}
|
||||
|
||||
return (
|
||||
<Form {...form}>
|
||||
<form action={formAction}>
|
||||
<SettingsContainer>
|
||||
{/* Name */}
|
||||
{!hidePolicyNameForm && <PolicyNameSection form={form} />}
|
||||
<PolicyUsersRolesSection
|
||||
// <Form {...form}>
|
||||
// <form action={formAction}>
|
||||
<SettingsContainer>
|
||||
{/* Name */}
|
||||
{!hidePolicyNameForm && <PolicyNameSection />}
|
||||
{/* <PolicyUsersRolesSection
|
||||
form={form}
|
||||
allRoles={allRoles}
|
||||
allUsers={allUsers}
|
||||
@@ -290,65 +289,123 @@ export function EditPolicyForm({
|
||||
<PolicyRulesSection
|
||||
form={form}
|
||||
isMaxmindAvailable={isMaxmindAvailable}
|
||||
isMaxmindAsnAvailable={isMaxmindAsnAvailable}
|
||||
/>
|
||||
</SettingsContainer>
|
||||
</form>
|
||||
</Form>
|
||||
isMaxmindAsnAvailable={isMaxmindASNAvailable}
|
||||
/> */}
|
||||
</SettingsContainer>
|
||||
// </form>
|
||||
// </Form>
|
||||
);
|
||||
}
|
||||
|
||||
// ─── PolicyNameSection ──────────────────────────────────────────────────
|
||||
type PolicyNameSectionProps = {
|
||||
form: UseFormReturn<PolicyFormValues, any, any>;
|
||||
isEditing?: boolean;
|
||||
};
|
||||
|
||||
export function PolicyNameSection({ form }: PolicyNameSectionProps) {
|
||||
export function PolicyNameSection() {
|
||||
const t = useTranslations();
|
||||
return (
|
||||
<SettingsSection>
|
||||
<SettingsSectionHeader>
|
||||
<SettingsSectionTitle>
|
||||
{t("resourcePolicyName")}
|
||||
</SettingsSectionTitle>
|
||||
<SettingsSectionDescription>
|
||||
{t("resourcePolicyNameDescription")}
|
||||
</SettingsSectionDescription>
|
||||
</SettingsSectionHeader>
|
||||
<SettingsSectionBody>
|
||||
<SettingsSectionForm>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="name"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t("name")}</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
{...field}
|
||||
placeholder={t(
|
||||
"resourcePolicyNamePlaceholder"
|
||||
)}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</SettingsSectionForm>
|
||||
</SettingsSectionBody>
|
||||
const api = createApiClient(useEnvContext());
|
||||
|
||||
<div className="flex py-6 justify-end">
|
||||
<Button
|
||||
type="submit"
|
||||
// loading={isSubmitting}
|
||||
// disabled={isSubmitting}
|
||||
>
|
||||
{t("saveSettings")}
|
||||
</Button>
|
||||
</div>
|
||||
</SettingsSection>
|
||||
const { policy } = useResourcePolicyContext();
|
||||
const { org } = useOrgContext();
|
||||
const form = useForm({
|
||||
resolver: zodResolver(
|
||||
z.object({
|
||||
name: z.string()
|
||||
})
|
||||
),
|
||||
defaultValues: {
|
||||
name: policy.name
|
||||
}
|
||||
});
|
||||
|
||||
const [, formAction, isSubmitting] = useActionState(onSubmit, null);
|
||||
|
||||
async function onSubmit() {
|
||||
const isValid = await form.trigger();
|
||||
|
||||
if (!isValid) return;
|
||||
|
||||
const payload = form.getValues();
|
||||
|
||||
try {
|
||||
const res = await api
|
||||
.put<AxiosResponse<ResourcePolicy>>(
|
||||
`/resource-policy/${policy.resourcePolicyId}`,
|
||||
{
|
||||
name: payload.name
|
||||
}
|
||||
)
|
||||
.catch((e) => {
|
||||
toast({
|
||||
variant: "destructive",
|
||||
title: t("policyErrorUpdate"),
|
||||
description: formatAxiosError(
|
||||
e,
|
||||
t("policyErrorUpdateDescription")
|
||||
)
|
||||
});
|
||||
});
|
||||
|
||||
if (res && res.status === 200) {
|
||||
toast({
|
||||
title: t("success"),
|
||||
description: t("policyUpdatedSuccess")
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
toast({
|
||||
variant: "destructive",
|
||||
title: t("policyErrorUpdate"),
|
||||
description: t("policyErrorUpdateMessageDescription")
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Form {...form}>
|
||||
<form action={formAction}>
|
||||
<SettingsSection>
|
||||
<SettingsSectionHeader>
|
||||
<SettingsSectionTitle>
|
||||
{t("resourcePolicyName")}
|
||||
</SettingsSectionTitle>
|
||||
<SettingsSectionDescription>
|
||||
{t("resourcePolicyNameDescription")}
|
||||
</SettingsSectionDescription>
|
||||
</SettingsSectionHeader>
|
||||
<SettingsSectionBody>
|
||||
<SettingsSectionForm>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="name"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t("name")}</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
{...field}
|
||||
placeholder={t(
|
||||
"resourcePolicyNamePlaceholder"
|
||||
)}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</SettingsSectionForm>
|
||||
</SettingsSectionBody>
|
||||
|
||||
<div className="flex py-6 justify-end">
|
||||
<Button
|
||||
type="submit"
|
||||
loading={isSubmitting}
|
||||
disabled={isSubmitting}
|
||||
>
|
||||
{t("saveSettings")}
|
||||
</Button>
|
||||
</div>
|
||||
</SettingsSection>
|
||||
</form>
|
||||
</Form>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
63
src/providers/ResourcePolicyProvider.tsx
Normal file
63
src/providers/ResourcePolicyProvider.tsx
Normal file
@@ -0,0 +1,63 @@
|
||||
"use client";
|
||||
|
||||
import { createContext, useContext, useState } from "react";
|
||||
import { useTranslations } from "next-intl";
|
||||
import type { GetResourcePolicyResponse } from "@server/routers/policy";
|
||||
|
||||
interface ResourcePolicyProviderProps {
|
||||
children: React.ReactNode;
|
||||
policy: GetResourcePolicyResponse;
|
||||
}
|
||||
|
||||
export function ResourcePolicyProvider({
|
||||
children,
|
||||
policy: serverPolicy
|
||||
}: ResourcePolicyProviderProps) {
|
||||
const [policy, setPolicy] =
|
||||
useState<GetResourcePolicyResponse>(serverPolicy);
|
||||
|
||||
const t = useTranslations();
|
||||
|
||||
const updatePolicy = (
|
||||
updatedPolicy: Partial<GetResourcePolicyResponse>
|
||||
) => {
|
||||
if (!policy) {
|
||||
throw new Error(t("resourceErrorNoUpdate"));
|
||||
}
|
||||
|
||||
setPolicy((prev) => {
|
||||
if (!prev) {
|
||||
return prev;
|
||||
}
|
||||
|
||||
return {
|
||||
...prev,
|
||||
...updatedPolicy
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<ResourcePolicyContext value={{ ...policy, updatePolicy }}>
|
||||
{children}
|
||||
</ResourcePolicyContext>
|
||||
);
|
||||
}
|
||||
|
||||
export type ResourcePolicyContextType = GetResourcePolicyResponse & {
|
||||
updatePolicy: (updatedPolicy: Partial<GetResourcePolicyResponse>) => void;
|
||||
};
|
||||
|
||||
export const ResourcePolicyContext = createContext<
|
||||
ResourcePolicyContextType | undefined
|
||||
>(undefined);
|
||||
|
||||
export function useResourcePolicyContext() {
|
||||
const context = useContext(ResourcePolicyContext);
|
||||
if (context === undefined) {
|
||||
throw new Error(
|
||||
"useResourcePolicyContext must be used within a ResourcePolicyProvider"
|
||||
);
|
||||
}
|
||||
return context;
|
||||
}
|
||||
Reference in New Issue
Block a user