From 24d7bcf0b3ce8cfea9f9da7206ac318bdf5e668b Mon Sep 17 00:00:00 2001 From: = Date: Tue, 9 Dec 2025 01:51:24 +0530 Subject: [PATCH] feat: improved ui based of feedback --- .../src/hooks/api/approvalRequests/types.ts | 2 +- .../components/ApprovalStepsSection.tsx | 3 +- .../PolicyTab/components/PoliciesTable.tsx | 18 +- .../PolicyTab/components/PolicyModal.tsx | 4 +- .../PolicySteps/PolicyConstraintsStep.tsx | 2 +- .../PolicySteps/PolicyDetailsStep.tsx | 6 +- .../PolicySteps/PolicyReviewStep.tsx | 6 +- .../components/PamAccountsTable.tsx | 15 ++ .../PamRequestAccountAccessModal.tsx | 187 ++++++++++++++++++ 9 files changed, 225 insertions(+), 18 deletions(-) create mode 100644 frontend/src/pages/pam/PamAccountsPage/components/PamRequestAccountAccessModal.tsx diff --git a/frontend/src/hooks/api/approvalRequests/types.ts b/frontend/src/hooks/api/approvalRequests/types.ts index 8885fb30d..2270d4988 100644 --- a/frontend/src/hooks/api/approvalRequests/types.ts +++ b/frontend/src/hooks/api/approvalRequests/types.ts @@ -51,7 +51,7 @@ export type ApprovalRequestStep = { export type PamAccessRequestData = { accountPath: string; - accessDuration: number; + accessDuration: string; }; export type TApprovalRequest = { diff --git a/frontend/src/pages/pam/ApprovalRequestDetailPage/components/ApprovalStepsSection.tsx b/frontend/src/pages/pam/ApprovalRequestDetailPage/components/ApprovalStepsSection.tsx index bd373b675..17bfae8a0 100644 --- a/frontend/src/pages/pam/ApprovalRequestDetailPage/components/ApprovalStepsSection.tsx +++ b/frontend/src/pages/pam/ApprovalRequestDetailPage/components/ApprovalStepsSection.tsx @@ -80,7 +80,6 @@ export const ApprovalStepsSection = ({ request }: Props) => {
{request.steps.map((step, index) => (
- {/* Connector line */} {index < request.steps.length - 1 && (
)} @@ -130,7 +129,7 @@ export const ApprovalStepsSection = ({ request }: Props) => {
{step.approvers.map((approver) => ( - {approver.type === ApproverType.Group ? : } + {approver.type === ApproverType.User ? : } {getApproverLabel(approver.id, approver.type)} ))} diff --git a/frontend/src/pages/pam/ApprovalsPage/components/PolicyTab/components/PoliciesTable.tsx b/frontend/src/pages/pam/ApprovalsPage/components/PolicyTab/components/PoliciesTable.tsx index afff6ae5b..bcfbf6220 100644 --- a/frontend/src/pages/pam/ApprovalsPage/components/PolicyTab/components/PoliciesTable.tsx +++ b/frontend/src/pages/pam/ApprovalsPage/components/PolicyTab/components/PoliciesTable.tsx @@ -98,7 +98,9 @@ export const PoliciesTable = ({ handlePopUpOpen }: Props) => { Policy Name - Max Request TTL + Max Approval Request TTL + Min Access Duration + Max Access Duration Conditions @@ -107,7 +109,7 @@ export const PoliciesTable = ({ handlePopUpOpen }: Props) => { {isPoliciesLoading && } {!isPoliciesLoading && policies.length === 0 && ( - + @@ -143,6 +145,8 @@ export const PoliciesTable = ({ handlePopUpOpen }: Props) => { {policy.name} {maxTtl} + {policy.constraints.constraints.accessDuration.min} + {policy.constraints.constraints.accessDuration.max} {conditionsCount} condition{conditionsCount !== 1 ? "s" : ""} @@ -197,11 +201,11 @@ export const PoliciesTable = ({ handlePopUpOpen }: Props) => { {isExpanded && ( - -
-
+ +
+
- Approval Contraints + Approval Conditions
{policy.conditions.conditions.map((step, index) => ( @@ -235,7 +239,7 @@ export const PoliciesTable = ({ handlePopUpOpen }: Props) => { ))}
-
+
Approval Sequence
diff --git a/frontend/src/pages/pam/ApprovalsPage/components/PolicyTab/components/PolicyModal.tsx b/frontend/src/pages/pam/ApprovalsPage/components/PolicyTab/components/PolicyModal.tsx index 0d92981d8..790ade93f 100644 --- a/frontend/src/pages/pam/ApprovalsPage/components/PolicyTab/components/PolicyModal.tsx +++ b/frontend/src/pages/pam/ApprovalsPage/components/PolicyTab/components/PolicyModal.tsx @@ -26,8 +26,8 @@ type Props = { }; const FORM_STEPS: { name: string; key: string; fields: (keyof TPolicyForm)[] }[] = [ - { name: "Details", key: "details", fields: ["name", "maxRequestTtl"] }, - { name: "Constraints", key: "constraints", fields: ["conditions", "constraints"] }, + { name: "Details", key: "details", fields: ["name", "maxRequestTtl", "constraints"] }, + { name: "Conditions", key: "conditions", fields: ["conditions"] }, { name: "Approvals", key: "approvals", fields: ["steps"] }, { name: "Review", key: "review", fields: [] } ]; diff --git a/frontend/src/pages/pam/ApprovalsPage/components/PolicyTab/components/PolicySteps/PolicyConstraintsStep.tsx b/frontend/src/pages/pam/ApprovalsPage/components/PolicyTab/components/PolicySteps/PolicyConstraintsStep.tsx index bfc17223d..52289ef14 100644 --- a/frontend/src/pages/pam/ApprovalsPage/components/PolicyTab/components/PolicySteps/PolicyConstraintsStep.tsx +++ b/frontend/src/pages/pam/ApprovalsPage/components/PolicyTab/components/PolicySteps/PolicyConstraintsStep.tsx @@ -66,7 +66,7 @@ export const PolicyConstraintsStep = () => { label="Account Paths" isError={Boolean(error)} errorText={error?.message} - helperText="Comma-separated account paths this condition applies to" + helperText="Matches any of the comma-separated account paths this condition applies to" > { } + label={} helperText="Maximum time-to-live for requests. Must be between 1 hour and 30 days. Leave empty for no limit." > @@ -41,10 +41,10 @@ export const PolicyDetailsStep = () => {

- Request Duration Constraints + PAM Account Access Duration TTL

- Set minimum and maximum duration (in seconds) for access requests + Set minimum and maximum duration (in seconds) for pam account access

diff --git a/frontend/src/pages/pam/ApprovalsPage/components/PolicyTab/components/PolicySteps/PolicyReviewStep.tsx b/frontend/src/pages/pam/ApprovalsPage/components/PolicyTab/components/PolicySteps/PolicyReviewStep.tsx index 10595015d..7dacb73c9 100644 --- a/frontend/src/pages/pam/ApprovalsPage/components/PolicyTab/components/PolicySteps/PolicyReviewStep.tsx +++ b/frontend/src/pages/pam/ApprovalsPage/components/PolicyTab/components/PolicySteps/PolicyReviewStep.tsx @@ -49,13 +49,15 @@ export const PolicyReviewStep = () => {
- +
-

Request Duration Constraints

+

+ PAM Account Access Duration TTL +

diff --git a/frontend/src/pages/pam/PamAccountsPage/components/PamAccountsTable.tsx b/frontend/src/pages/pam/PamAccountsPage/components/PamAccountsTable.tsx index f19a1a308..61a09c1b0 100644 --- a/frontend/src/pages/pam/PamAccountsPage/components/PamAccountsTable.tsx +++ b/frontend/src/pages/pam/PamAccountsPage/components/PamAccountsTable.tsx @@ -8,6 +8,7 @@ import { faFilter, faFolderPlus, faMagnifyingGlass, + faPen, faPlus, faSearch } from "@fortawesome/free-solid-svg-icons"; @@ -67,6 +68,7 @@ import { PamAddFolderModal } from "./PamAddFolderModal"; import { PamDeleteAccountModal } from "./PamDeleteAccountModal"; import { PamDeleteFolderModal } from "./PamDeleteFolderModal"; import { PamFolderRow } from "./PamFolderRow"; +import { PamRequestAccountAccessModal } from "./PamRequestAccountAccessModal"; import { PamUpdateAccountModal } from "./PamUpdateAccountModal"; import { PamUpdateFolderModal } from "./PamUpdateFolderModal"; import { useAccessAwsIamAccount } from "./useAccessAwsIamAccount"; @@ -90,6 +92,7 @@ export const PamAccountsTable = ({ projectId }: Props) => { "deleteFolder", "addAccount", "accessAccount", + "requestAccount", "updateAccount", "deleteAccount" ] as const); @@ -323,6 +326,14 @@ export const PamAccountsTable = ({ projectId }: Props) => { )} + { } projectId={projectId} /> + handlePopUpToggle("requestAccount", isOpen)} + /> handlePopUpToggle("deleteAccount", isOpen)} diff --git a/frontend/src/pages/pam/PamAccountsPage/components/PamRequestAccountAccessModal.tsx b/frontend/src/pages/pam/PamAccountsPage/components/PamRequestAccountAccessModal.tsx new file mode 100644 index 000000000..0e37d8af6 --- /dev/null +++ b/frontend/src/pages/pam/PamAccountsPage/components/PamRequestAccountAccessModal.tsx @@ -0,0 +1,187 @@ +import { useMemo } from "react"; +import { Controller, useForm } from "react-hook-form"; +import { zodResolver } from "@hookform/resolvers/zod"; +import ms from "ms"; +import { z } from "zod"; + +import { TtlFormLabel } from "@app/components/features"; +import { createNotification } from "@app/components/notifications"; +import { + Button, + FormControl, + Input, + Modal, + ModalClose, + ModalContent, + TextArea +} from "@app/components/v2"; +import { useProject } from "@app/context"; +import { ApprovalPolicyType } from "@app/hooks/api/approvalPolicies"; +import { useCreateApprovalRequest } from "@app/hooks/api/approvalRequests/mutations"; +import { TPamAccount } from "@app/hooks/api/pam"; + +type Props = { + account?: TPamAccount; + accountPath?: string; + isOpen: boolean; + onOpenChange: (isOpen: boolean) => void; +}; + +const formSchema = z.object({ + accountPath: z.string().min(1, "Account path is required"), + accessDuration: z + .string() + .min(1, "Access duration is required") + .refine( + (value) => { + try { + const duration = ms(value); + return duration > 0; + } catch { + return false; + } + }, + { message: "Invalid duration format. Use formats like: 1h, 3d, 30m" } + ), + justification: z.string().max(512).optional() +}); + +type FormData = z.infer; + +const Content = ({ onOpenChange, account, accountPath }: Props) => { + const { projectId } = useProject(); + const { mutateAsync: createApprovalRequest, isPending: isSubmitting } = + useCreateApprovalRequest(); + + const fullAccountPath = useMemo(() => { + const accountName = account?.name ?? ""; + if (accountPath) { + const path = accountPath.replace(/^\/+|\/+$/g, ""); + return `${path}/${accountName}`; + } + return accountName; + }, [account, accountPath]); + + const form = useForm({ + resolver: zodResolver(formSchema), + defaultValues: { + accountPath: fullAccountPath, + accessDuration: "4h", + justification: "" + } + }); + + const { + control, + handleSubmit, + formState: { isDirty } + } = form; + + const onSubmit = async (formData: FormData) => { + try { + await createApprovalRequest({ + policyType: ApprovalPolicyType.PamAccess, + projectId, + justification: formData.justification || null, + requestData: { + accountPath: formData.accountPath, + accessDuration: formData.accessDuration + } + }); + + createNotification({ + text: "Access request submitted successfully", + type: "success" + }); + + onOpenChange(false); + } catch (error) { + console.error(error); + createNotification({ + text: "Failed to submit access request", + type: "error" + }); + } + }; + + return ( +
+ ( + + + + )} + /> + ( + } + helperText="Duration of access requested" + errorText={error?.message} + isError={Boolean(error?.message)} + > + + + )} + /> + ( + +