diff --git a/frontend/src/const/routes.ts b/frontend/src/const/routes.ts index 32555eea7..ee922f9e6 100644 --- a/frontend/src/const/routes.ts +++ b/frontend/src/const/routes.ts @@ -366,10 +366,6 @@ export const ROUTE_PATHS = Object.freeze({ PamSessionByIDPage: setRoute( "/organizations/$orgId/projects/pam/$projectId/sessions/$sessionId", "/_authenticate/_inject-org-details/_org-layout/organizations/$orgId/projects/pam/$projectId/_pam-layout/sessions/$sessionId" - ), - ApprovalRequestDetailPage: setRoute( - "/organizations/$orgId/projects/pam/$projectId/approval-requests/$approvalRequestId", - "/_authenticate/_inject-org-details/_org-layout/organizations/$orgId/projects/pam/$projectId/_pam-layout/approval-requests/$approvalRequestId" ) }, Public: { diff --git a/frontend/src/hooks/api/approvalGrants/index.tsx b/frontend/src/hooks/api/approvalGrants/index.tsx new file mode 100644 index 000000000..140fb4bac --- /dev/null +++ b/frontend/src/hooks/api/approvalGrants/index.tsx @@ -0,0 +1,10 @@ +export { useRevokeApprovalGrant } from "./mutations"; +export { approvalGrantQuery } from "./queries"; +export { + ApprovalGrantStatus, + type PamAccessGrantAttributes, + type TApprovalGrant, + type TGetApprovalGrantByIdDTO, + type TListApprovalGrantsDTO, + type TRevokeApprovalGrantDTO +} from "./types"; diff --git a/frontend/src/hooks/api/approvalGrants/mutations.tsx b/frontend/src/hooks/api/approvalGrants/mutations.tsx new file mode 100644 index 000000000..b28ff45f9 --- /dev/null +++ b/frontend/src/hooks/api/approvalGrants/mutations.tsx @@ -0,0 +1,23 @@ +import { useMutation, useQueryClient } from "@tanstack/react-query"; + +import { apiRequest } from "@app/config/request"; + +import { approvalGrantQuery } from "./queries"; +import { TApprovalGrant, TRevokeApprovalGrantDTO } from "./types"; + +export const useRevokeApprovalGrant = () => { + const queryClient = useQueryClient(); + + return useMutation({ + mutationFn: async ({ policyType, grantId, revocationReason }: TRevokeApprovalGrantDTO) => { + const { data } = await apiRequest.post<{ grant: TApprovalGrant }>( + `/api/v1/approval-policies/${policyType}/grants/${grantId}/revoke`, + { revocationReason } + ); + return data.grant; + }, + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: approvalGrantQuery.allKey() }); + } + }); +}; diff --git a/frontend/src/hooks/api/approvalGrants/queries.tsx b/frontend/src/hooks/api/approvalGrants/queries.tsx new file mode 100644 index 000000000..ca9334ca7 --- /dev/null +++ b/frontend/src/hooks/api/approvalGrants/queries.tsx @@ -0,0 +1,37 @@ +import { queryOptions } from "@tanstack/react-query"; + +import { apiRequest } from "@app/config/request"; + +import { TApprovalGrant, TGetApprovalGrantByIdDTO, TListApprovalGrantsDTO } from "./types"; + +export const approvalGrantQuery = { + allKey: () => ["approval-grants"] as const, + getByIdKey: (params: TGetApprovalGrantByIdDTO) => + [...approvalGrantQuery.allKey(), "by-id", params] as const, + listKey: (params: TListApprovalGrantsDTO) => + [...approvalGrantQuery.allKey(), "list", params] as const, + getById: (params: TGetApprovalGrantByIdDTO) => + queryOptions({ + queryKey: approvalGrantQuery.getByIdKey(params), + queryFn: async () => { + const { data } = await apiRequest.get<{ grant: TApprovalGrant }>( + `/api/v1/approval-policies/${params.policyType}/grants/${params.grantId}` + ); + return data.grant; + } + }), + list: (params: TListApprovalGrantsDTO) => + queryOptions({ + queryKey: approvalGrantQuery.listKey(params), + queryFn: async () => { + const { data } = await apiRequest.get<{ + grants: TApprovalGrant[]; + }>(`/api/v1/approval-policies/${params.policyType}/grants`, { + params: { + projectId: params.projectId + } + }); + return data.grants; + } + }) +}; diff --git a/frontend/src/hooks/api/approvalGrants/types.ts b/frontend/src/hooks/api/approvalGrants/types.ts new file mode 100644 index 000000000..852529259 --- /dev/null +++ b/frontend/src/hooks/api/approvalGrants/types.ts @@ -0,0 +1,46 @@ +import { ApprovalPolicyType } from "../approvalPolicies"; + +export enum ApprovalGrantStatus { + Active = "active", + Expired = "expired", + Revoked = "revoked" +} + +// PAM Access Grant Attributes +export type PamAccessGrantAttributes = { + accountPath: string; + accessDuration: string; +}; + +// Base Grant Type +export type TApprovalGrant = { + id: string; + projectId: string; + requestId: string | null; + granteeUserId: string | null; + revokedByUserId: string | null; + revocationReason: string | null; + status: ApprovalGrantStatus; + type: ApprovalPolicyType; + attributes: PamAccessGrantAttributes; + createdAt: string; + expiresAt: string | null; + revokedAt: string | null; +}; + +// DTOs +export type TListApprovalGrantsDTO = { + policyType: ApprovalPolicyType; + projectId: string; +}; + +export type TGetApprovalGrantByIdDTO = { + policyType: ApprovalPolicyType; + grantId: string; +}; + +export type TRevokeApprovalGrantDTO = { + policyType: ApprovalPolicyType; + grantId: string; + revocationReason?: string; +}; diff --git a/frontend/src/hooks/api/approvalPolicies/types.ts b/frontend/src/hooks/api/approvalPolicies/types.ts index 3843cc583..8fb461814 100644 --- a/frontend/src/hooks/api/approvalPolicies/types.ts +++ b/frontend/src/hooks/api/approvalPolicies/types.ts @@ -18,14 +18,13 @@ export type ApprovalPolicyStep = { }; export type PamAccessPolicyConditions = { - resourceIds: string[]; accountPaths: string[]; }[]; export type PamAccessPolicyConstraints = { - requestDurationSeconds: { - min: number; - max: number; + accessDuration: { + min: string; + max: string; }; }; @@ -33,7 +32,7 @@ export type TApprovalPolicy = { id: string; projectId: string; name: string; - maxRequestTtlSeconds?: number | null; + maxRequestTtl?: string | null; type: ApprovalPolicyType; conditions: { version: number; @@ -52,7 +51,7 @@ export type TCreateApprovalPolicyDTO = { policyType: ApprovalPolicyType; projectId: string; name: string; - maxRequestTtlSeconds?: number | null; + maxRequestTtl?: string | null; conditions: PamAccessPolicyConditions; constraints: PamAccessPolicyConstraints; steps: ApprovalPolicyStep[]; @@ -62,7 +61,7 @@ export type TUpdateApprovalPolicyDTO = { policyType: ApprovalPolicyType; policyId: string; name?: string; - maxRequestTtlSeconds?: number | null; + maxRequestTtl?: string | null; conditions?: PamAccessPolicyConditions; constraints?: PamAccessPolicyConstraints; steps?: ApprovalPolicyStep[]; diff --git a/frontend/src/hooks/api/approvalRequests/index.tsx b/frontend/src/hooks/api/approvalRequests/index.tsx index c6db021b0..2b146836a 100644 --- a/frontend/src/hooks/api/approvalRequests/index.tsx +++ b/frontend/src/hooks/api/approvalRequests/index.tsx @@ -1,5 +1,6 @@ export { useApproveApprovalRequest, + useCancelApprovalRequest, useCreateApprovalRequest, useRejectApprovalRequest } from "./mutations"; diff --git a/frontend/src/hooks/api/approvalRequests/mutations.tsx b/frontend/src/hooks/api/approvalRequests/mutations.tsx index 3afcd290a..81e14b311 100644 --- a/frontend/src/hooks/api/approvalRequests/mutations.tsx +++ b/frontend/src/hooks/api/approvalRequests/mutations.tsx @@ -6,6 +6,7 @@ import { approvalRequestQuery } from "./queries"; import { TApprovalRequest, TApproveApprovalRequestDTO, + TCancelApprovalRequestDTO, TCreateApprovalRequestDTO, TRejectApprovalRequestDTO } from "./types"; @@ -57,3 +58,18 @@ export const useRejectApprovalRequest = () => { } }); }; + +export const useCancelApprovalRequest = () => { + const queryClient = useQueryClient(); + return useMutation({ + mutationFn: async ({ policyType, requestId }: TCancelApprovalRequestDTO) => { + const { data } = await apiRequest.post<{ request: TApprovalRequest }>( + `/api/v1/approval-policies/${policyType}/requests/${requestId}/cancel` + ); + return data.request; + }, + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: approvalRequestQuery.allKey() }); + } + }); +}; diff --git a/frontend/src/hooks/api/approvalRequests/types.ts b/frontend/src/hooks/api/approvalRequests/types.ts index d57728353..f59214b3e 100644 --- a/frontend/src/hooks/api/approvalRequests/types.ts +++ b/frontend/src/hooks/api/approvalRequests/types.ts @@ -49,9 +49,8 @@ export type ApprovalRequestStep = { }; export type PamAccessRequestData = { - resourceId: string; accountPath: string; - requestDurationSeconds: number; + accessDuration: number; }; export type TApprovalRequest = { @@ -78,7 +77,7 @@ export type TCreateApprovalRequestDTO = { policyType: ApprovalPolicyType; projectId: string; justification?: string | null; - expiresAt?: Date | null; + requestDuration?: string | null; requestData: PamAccessRequestData; }; @@ -103,3 +102,8 @@ export type TRejectApprovalRequestDTO = { requestId: string; comment?: string; }; + +export type TCancelApprovalRequestDTO = { + policyType: ApprovalPolicyType; + requestId: string; +}; diff --git a/frontend/src/hooks/api/index.tsx b/frontend/src/hooks/api/index.tsx index 33eacd18d..acbf1d9f5 100644 --- a/frontend/src/hooks/api/index.tsx +++ b/frontend/src/hooks/api/index.tsx @@ -1,6 +1,9 @@ export * from "./accessApproval"; export * from "./admin"; export * from "./apiKeys"; +export * from "./approvalGrants"; +export * from "./approvalPolicies"; +export * from "./approvalRequests"; export * from "./assumePrivileges"; export * from "./auditLogs"; export * from "./auditLogStreams"; diff --git a/frontend/src/pages/pam/ApprovalRequestDetailPage/ApprovalRequestDetailPage.tsx b/frontend/src/pages/pam/ApprovalRequestDetailPage/ApprovalRequestDetailPage.tsx index eb95eb2d0..2d12b8c68 100644 --- a/frontend/src/pages/pam/ApprovalRequestDetailPage/ApprovalRequestDetailPage.tsx +++ b/frontend/src/pages/pam/ApprovalRequestDetailPage/ApprovalRequestDetailPage.tsx @@ -2,7 +2,7 @@ import { Helmet } from "react-helmet"; import { faBan, faChevronLeft } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { useQuery } from "@tanstack/react-query"; -import { Link, useNavigate, useParams } from "@tanstack/react-router"; +import { Link, useParams } from "@tanstack/react-router"; import { ContentLoader, EmptyState, PageHeader } from "@app/components/v2"; import { ROUTE_PATHS } from "@app/const/routes"; @@ -61,7 +61,7 @@ const PageContent = () => {
diff --git a/frontend/src/pages/pam/ApprovalRequestDetailPage/components/RequestActionsSection.tsx b/frontend/src/pages/pam/ApprovalRequestDetailPage/components/RequestActionsSection.tsx index 1eff54685..1fb216ebd 100644 --- a/frontend/src/pages/pam/ApprovalRequestDetailPage/components/RequestActionsSection.tsx +++ b/frontend/src/pages/pam/ApprovalRequestDetailPage/components/RequestActionsSection.tsx @@ -5,7 +5,6 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { createNotification } from "@app/components/notifications"; import { Button, - FormControl, FormLabel, Popover, PopoverContent, diff --git a/frontend/src/pages/pam/ApprovalRequestDetailPage/components/RequestDetailsSection.tsx b/frontend/src/pages/pam/ApprovalRequestDetailPage/components/RequestDetailsSection.tsx index 9230a0c03..752021595 100644 --- a/frontend/src/pages/pam/ApprovalRequestDetailPage/components/RequestDetailsSection.tsx +++ b/frontend/src/pages/pam/ApprovalRequestDetailPage/components/RequestDetailsSection.tsx @@ -23,18 +23,8 @@ const getStatusBadgeColor = (status: ApprovalRequestStatus) => { } }; -const formatDuration = (seconds: number) => { - const hours = Math.floor(seconds / 3600); - const minutes = Math.floor((seconds % 3600) / 60); - - if (hours > 0) { - return `${hours}h${minutes > 0 ? ` ${minutes}m` : ""}`; - } - return `${minutes}m`; -}; - export const RequestDetailsSection = ({ request }: Props) => { - const { accountPath, requestDurationSeconds } = request.requestData.requestData; + const { accountPath, accessDuration } = request.requestData.requestData; return (
@@ -53,9 +43,7 @@ export const RequestDetailsSection = ({ request }: Props) => { {request.requesterEmail} {accountPath} - - {formatDuration(requestDurationSeconds)} - + {accessDuration} {request.justification && (

diff --git a/frontend/src/pages/pam/ApprovalsPage/components/ApprovalRequestTab/components/RequestsTable.tsx b/frontend/src/pages/pam/ApprovalsPage/components/ApprovalRequestTab/components/RequestsTable.tsx index aec25cb60..85eb9f1d9 100644 --- a/frontend/src/pages/pam/ApprovalsPage/components/ApprovalRequestTab/components/RequestsTable.tsx +++ b/frontend/src/pages/pam/ApprovalsPage/components/ApprovalRequestTab/components/RequestsTable.tsx @@ -63,16 +63,6 @@ const getStatusBadgeColor = (status: ApprovalRequestStatus) => { } }; -const formatDuration = (seconds: number) => { - const hours = Math.floor(seconds / 3600); - const minutes = Math.floor((seconds % 3600) / 60); - - if (hours > 0) { - return `${hours}h${minutes > 0 ? ` ${minutes}m` : ""}`; - } - return `${minutes}m`; -}; - const checkIfUserNeedsToApprove = ( request: TApprovalRequest, userId: string, @@ -131,7 +121,6 @@ export const RequestsTable = () => { request.requesterName?.toLowerCase().includes(search.toLowerCase()) || request.requesterEmail?.toLowerCase().includes(search.toLowerCase()) || request.justification?.toLowerCase().includes(search.toLowerCase()) || - request.requestData.requestData.resourceId.toLowerCase().includes(search.toLowerCase()) || request.requestData.requestData.accountPath.toLowerCase().includes(search.toLowerCase()) ); } @@ -272,7 +261,7 @@ export const RequestsTable = () => { {!isRequestsLoading && paginatedRequests.map((request) => { const needsApproval = checkIfUserNeedsToApprove(request, userId, userGroups); - const { accountPath, requestDurationSeconds } = request.requestData.requestData; + const { accountPath, accessDuration } = request.requestData.requestData; return ( {

- - {formatDuration(requestDurationSeconds)} - + {accessDuration}
- - {request.status} + + {request.status.split("-").join(" ")} {needsApproval && (
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 6a2aac44b..4aa0d1abe 100644 --- a/frontend/src/pages/pam/ApprovalsPage/components/PolicyTab/components/PoliciesTable.tsx +++ b/frontend/src/pages/pam/ApprovalsPage/components/PolicyTab/components/PoliciesTable.tsx @@ -37,6 +37,8 @@ import { ApproverType } from "@app/hooks/api/approvalPolicies"; import { UsePopUpState } from "@app/hooks/usePopUp"; +import { Badge } from "@app/components/v3"; +import { User, Users } from "lucide-react"; type Props = { handlePopUpOpen: ( @@ -113,9 +115,7 @@ export const PoliciesTable = ({ handlePopUpOpen }: Props) => { {!isPoliciesLoading && policies.map((policy) => { const isExpanded = expandedRows.has(policy.id); - const maxTtl = policy.maxRequestTtlSeconds - ? `${Math.floor(policy.maxRequestTtlSeconds / 3600)}h` - : "No limit"; + const maxTtl = policy.maxRequestTtl ? policy.maxRequestTtl : "No limit"; const conditionsCount = policy.conditions.conditions.length; return ( @@ -198,8 +198,8 @@ export const PoliciesTable = ({ handlePopUpOpen }: Props) => { {isExpanded && ( -
-
+
+
Approval Contraints
@@ -211,27 +211,6 @@ export const PoliciesTable = ({ handlePopUpOpen }: Props) => { )} >
-
- - Resources: - -

- {step.resourceIds.join(", ")} -

-
-
-
- - AND - -
-
Account Paths: @@ -256,7 +235,7 @@ export const PoliciesTable = ({ handlePopUpOpen }: Props) => { ))}
-
+
Approval Sequence
@@ -264,11 +243,11 @@ export const PoliciesTable = ({ handlePopUpOpen }: Props) => {
-
+
Step {index + 1} {step.name && ( @@ -282,21 +261,19 @@ export const PoliciesTable = ({ handlePopUpOpen }: Props) => { {step.requiredApprovals !== 1 ? "s" : ""}
-
+
{step.approvers.map((approver, approverIndex) => ( -
- - {approver.type}:{" "} - - {getApproverLabel(approver.id, approver.type)} - -
+ {approver.type === ApproverType.Group ? ( + + ) : ( + + )} + {getApproverLabel(approver.id, approver.type)} + ))}
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 a939ff7dd..0d92981d8 100644 --- a/frontend/src/pages/pam/ApprovalsPage/components/PolicyTab/components/PolicyModal.tsx +++ b/frontend/src/pages/pam/ApprovalsPage/components/PolicyTab/components/PolicyModal.tsx @@ -26,7 +26,7 @@ type Props = { }; const FORM_STEPS: { name: string; key: string; fields: (keyof TPolicyForm)[] }[] = [ - { name: "Details", key: "details", fields: ["name", "maxRequestTtlSeconds"] }, + { name: "Details", key: "details", fields: ["name", "maxRequestTtl"] }, { name: "Constraints", key: "constraints", fields: ["conditions", "constraints"] }, { name: "Approvals", key: "approvals", fields: ["steps"] }, { name: "Review", key: "review", fields: [] } @@ -45,12 +45,12 @@ export const PolicyModal = ({ popUp, handlePopUpToggle }: Props) => { resolver: zodResolver(PolicyFormSchema), defaultValues: { name: "", - maxRequestTtlSeconds: null, - conditions: [{ resourceIds: [], accountPaths: [] }], + maxRequestTtl: null, + conditions: [{ accountPaths: [] }], constraints: { - requestDurationSeconds: { - min: 30, - max: 604800 + accessDuration: { + min: "30s", + max: "7d" } }, steps: [ @@ -74,7 +74,7 @@ export const PolicyModal = ({ popUp, handlePopUpToggle }: Props) => { if (policyData?.policy) { reset({ name: policyData.policy.name, - maxRequestTtlSeconds: policyData.policy.maxRequestTtlSeconds, + maxRequestTtl: policyData.policy.maxRequestTtl, conditions: policyData.policy.conditions.conditions, constraints: policyData.policy.constraints.constraints, steps: policyData.policy.steps.map((step) => ({ @@ -85,12 +85,12 @@ export const PolicyModal = ({ popUp, handlePopUpToggle }: Props) => { } else { reset({ name: "", - maxRequestTtlSeconds: null, - conditions: [{ resourceIds: [], accountPaths: [] }], + maxRequestTtl: null, + conditions: [{ accountPaths: [] }], constraints: { - requestDurationSeconds: { - min: 30, - max: 604800 + accessDuration: { + min: "30s", + max: "7d" } }, steps: [ diff --git a/frontend/src/pages/pam/ApprovalsPage/components/PolicyTab/components/PolicySchema.tsx b/frontend/src/pages/pam/ApprovalsPage/components/PolicyTab/components/PolicySchema.tsx index 6c861b237..ff76ddd1b 100644 --- a/frontend/src/pages/pam/ApprovalsPage/components/PolicyTab/components/PolicySchema.tsx +++ b/frontend/src/pages/pam/ApprovalsPage/components/PolicyTab/components/PolicySchema.tsx @@ -1,21 +1,41 @@ +import ms from "ms"; import { z } from "zod"; import { ApproverType } from "@app/hooks/api/approvalPolicies"; +// 30 to 7 days +const DurationSchema = ( + min = 30, + max = 604800, + msg = "Duration must be between 30 seconds and 7 days" +) => + z.string().refine( + (val) => { + const duration = ms(val) / 1000; + + // 30 seconds to 7 days + return duration >= min && duration <= max; + }, + { message: msg } + ); + export const PolicyFormSchema = z.object({ name: z.string().min(1, "Policy name is required").max(128), - maxRequestTtlSeconds: z.number().min(3600).max(2592000).nullable().optional(), + maxRequestTtl: DurationSchema( + 3600, + 2592000, + "Duration must be between 1 hour and 30 days" + ).nullish(), conditions: z .object({ - resourceIds: z.array(z.string().uuid()), accountPaths: z.array(z.string().min(1)) }) .array() .min(1, "At least one condition is required"), constraints: z.object({ - requestDurationSeconds: z.object({ - min: z.number().min(0).max(604800), - max: z.number().min(1).max(604800) + accessDuration: z.object({ + min: DurationSchema(), + max: DurationSchema() }) }), steps: z 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 a9ee8c20a..bfc17223d 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 @@ -33,7 +33,7 @@ export const PolicyConstraintsStep = () => { variant="outline_bg" size="xs" leftIcon={} - onClick={() => appendCondition({ resourceIds: [], accountPaths: [] })} + onClick={() => appendCondition({ accountPaths: [] })} > Add Condition @@ -58,35 +58,6 @@ export const PolicyConstraintsStep = () => { )}
- ( - - { - const ids = e.target.value - .split(",") - .map((id) => id.trim()) - .filter(Boolean); - resourceField.onChange(ids); - }} - placeholder="e.g., 550e8400-e29b-41d4-a716-446655440000, ..." - /> - - )} - /> -
-
- AND -
-
{ .filter(Boolean); pathField.onChange(paths); }} - placeholder="e.g., /admin/*, /users/john" + placeholder="e.g., /admin/**, /users/john, /**" /> )} diff --git a/frontend/src/pages/pam/ApprovalsPage/components/PolicyTab/components/PolicySteps/PolicyDetailsStep.tsx b/frontend/src/pages/pam/ApprovalsPage/components/PolicyTab/components/PolicySteps/PolicyDetailsStep.tsx index ca9e12644..649fa1f34 100644 --- a/frontend/src/pages/pam/ApprovalsPage/components/PolicyTab/components/PolicySteps/PolicyDetailsStep.tsx +++ b/frontend/src/pages/pam/ApprovalsPage/components/PolicyTab/components/PolicySteps/PolicyDetailsStep.tsx @@ -1,5 +1,6 @@ import { Controller, useFormContext } from "react-hook-form"; +import { TtlFormLabel } from "@app/components/features"; import { FormControl, Input } from "@app/components/v2"; import { TPolicyForm } from "../PolicySchema"; @@ -25,24 +26,15 @@ export const PolicyDetailsStep = () => { /> ( } + helperText="Maximum time-to-live for requests. Must be between 1 hour and 30 days. Leave empty for no limit." > - { - const val = e.target.value; - field.onChange(val === "" ? null : parseInt(val, 10)); - }} - placeholder="e.g., 86400 (24 hours)" - /> + )} /> @@ -58,41 +50,29 @@ export const PolicyDetailsStep = () => {
( } isError={Boolean(error)} errorText={error?.message} - helperText="30-604800 hours" + helperText="Must be between 30s and 7 days" > - field.onChange(parseInt(e.target.value, 10))} - /> + )} /> ( } isError={Boolean(error)} errorText={error?.message} - helperText="30-604800 hours (7 days)" + helperText="Must be between 30s and 7 days" > - field.onChange(parseInt(e.target.value, 10))} - /> + )} /> 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 12dff585b..10595015d 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 @@ -24,17 +24,7 @@ export const PolicyReviewStep = () => { const { data: members = [] } = useGetWorkspaceUsers(projectId); const { data: groups = [] } = useListWorkspaceGroups(projectId); - const { name, maxRequestTtlSeconds, conditions, constraints, steps } = watch(); - - const formatTtl = (seconds: number | null | undefined) => { - if (!seconds) return "No limit"; - const hours = Math.floor(seconds / 3600); - const days = Math.floor(hours / 24); - if (days > 0) { - return `${days} day${days !== 1 ? "s" : ""} (${seconds}s)`; - } - return `${hours} hour${hours !== 1 ? "s" : ""} (${seconds}s)`; - }; + const { name, maxRequestTtl, conditions, constraints, steps } = watch(); const getApproverLabel = (approverId: string, approverType: ApproverType) => { if (approverType === ApproverType.User) { @@ -59,7 +49,7 @@ export const PolicyReviewStep = () => {
- +
@@ -68,14 +58,8 @@ export const PolicyReviewStep = () => {

Request Duration Constraints

- - + +
@@ -95,14 +79,6 @@ export const PolicyReviewStep = () => { Condition {index + 1}
-
- Resource IDs: - - {condition.resourceIds.length > 0 - ? condition.resourceIds.join(", ") - : "None specified"} - -
Account Paths: diff --git a/frontend/src/pages/pam/ApprovalsPage/components/RequestGrantTab/RequestGrantTab.tsx b/frontend/src/pages/pam/ApprovalsPage/components/RequestGrantTab/RequestGrantTab.tsx new file mode 100644 index 000000000..564b1cf4f --- /dev/null +++ b/frontend/src/pages/pam/ApprovalsPage/components/RequestGrantTab/RequestGrantTab.tsx @@ -0,0 +1,3 @@ +export const RequestGrantTab = () => { + return
Grant list
; +};