Merge pull request #5009 from Infisical/ga-improvements

improvement(global-approvals): basic UI improvements
This commit is contained in:
Andre
2025-12-09 14:14:50 -05:00
committed by GitHub
17 changed files with 127 additions and 269 deletions
@@ -119,8 +119,8 @@ const PageContent = () => {
)} )}
</div> </div>
</PageHeader> </PageHeader>
<div className="flex justify-center gap-4"> <div className="flex flex-col justify-center gap-4 lg:flex-row">
<div className="flex w-96 flex-col gap-4"> <div className="flex flex-col gap-4 lg:w-96">
<RequestDetailsSection request={request} /> <RequestDetailsSection request={request} />
<RequestActionsSection request={request} /> <RequestActionsSection request={request} />
</div> </div>
@@ -75,7 +75,7 @@ export const ApprovalStepsSection = ({ request }: Props) => {
return ( return (
<div className="flex w-full flex-col gap-3 rounded-lg border border-mineshaft-600 bg-mineshaft-900 px-4 py-3"> <div className="flex w-full flex-col gap-3 rounded-lg border border-mineshaft-600 bg-mineshaft-900 px-4 py-3">
<div className="mb-2 flex items-center justify-between border-b border-mineshaft-500 pb-2"> <div className="mb-2 flex items-center justify-between border-b border-mineshaft-500 pb-2">
<h3 className="text-lg font-medium text-mineshaft-100">Approval Workflow</h3> <h3 className="text-lg font-medium text-mineshaft-100">Approval Sequence</h3>
</div> </div>
<div className="space-y-6"> <div className="space-y-6">
{request.steps.map((step, index) => ( {request.steps.map((step, index) => (
@@ -127,7 +127,7 @@ export const RequestActionsSection = ({ request }: Props) => {
Review Review
</Button> </Button>
</PopoverTrigger> </PopoverTrigger>
<PopoverContent align="start" title="Finish your review" className="w-96"> <PopoverContent align="start" title="Finish your review" className="w-96 pt-4">
<FormLabel label="Finish your review" /> <FormLabel label="Finish your review" />
<TextArea <TextArea
value={comment} value={comment}
@@ -43,7 +43,7 @@ export const RequestDetailsSection = ({ request }: Props) => {
</GenericFieldLabel> </GenericFieldLabel>
<GenericFieldLabel label="Requester Email">{request.requesterEmail}</GenericFieldLabel> <GenericFieldLabel label="Requester Email">{request.requesterEmail}</GenericFieldLabel>
<GenericFieldLabel label="Account Path">{accountPath}</GenericFieldLabel> <GenericFieldLabel label="Account Path">{accountPath}</GenericFieldLabel>
<GenericFieldLabel label="Duration">{accessDuration}</GenericFieldLabel> <GenericFieldLabel label="Access Duration">{accessDuration}</GenericFieldLabel>
{request.justification && ( {request.justification && (
<GenericFieldLabel label="Justification"> <GenericFieldLabel label="Justification">
<p className="rounded-sm bg-mineshaft-600 p-2 text-xs break-words"> <p className="rounded-sm bg-mineshaft-600 p-2 text-xs break-words">
@@ -152,8 +152,11 @@ export const ApprovalRequestTab = () => {
return ( return (
<div className="mb-6 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4"> <div className="mb-6 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
<div className="mb-4 flex items-center justify-between"> <div className="mb-4">
<p className="text-xl font-medium text-mineshaft-100">Approval Requests</p> <div className="flex items-center gap-x-2">
<p className="text-xl font-medium text-mineshaft-100">Access Requests</p>
</div>
<p className="text-sm text-bunker-300">View pending access requests to PAM accounts</p>
</div> </div>
<div> <div>
<div className="flex gap-2"> <div className="flex gap-2">
@@ -262,7 +265,7 @@ export const ApprovalRequestTab = () => {
value={search} value={search}
onChange={(e) => setSearch(e.target.value)} onChange={(e) => setSearch(e.target.value)}
leftIcon={<FontAwesomeIcon icon={faMagnifyingGlass} />} leftIcon={<FontAwesomeIcon icon={faMagnifyingGlass} />}
placeholder="Search approval requests..." placeholder="Search access requests..."
/> />
</div> </div>
<TableContainer className="mt-4"> <TableContainer className="mt-4">
@@ -271,7 +274,7 @@ export const ApprovalRequestTab = () => {
<Tr> <Tr>
<Th>Requester</Th> <Th>Requester</Th>
<Th>Account Path</Th> <Th>Account Path</Th>
<Th>Duration</Th> <Th>Access Duration</Th>
<Th>Status</Th> <Th>Status</Th>
<Th>Requested</Th> <Th>Requested</Th>
</Tr> </Tr>
@@ -286,10 +289,7 @@ export const ApprovalRequestTab = () => {
return ( return (
<Tr <Tr
key={request.id} key={request.id}
className={twMerge( className="cursor-pointer transition-colors hover:bg-mineshaft-700"
"cursor-pointer transition-colors hover:bg-mineshaft-700",
needsApproval && "bg-primary/5 hover:bg-primary/10"
)}
onClick={() => handleRowClick(request.id)} onClick={() => handleRowClick(request.id)}
> >
<Td> <Td>
@@ -38,10 +38,14 @@ export const PolicyTab = () => {
return ( return (
<div className="mb-6 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4"> <div className="mb-6 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
<div className="mb-4 flex items-center justify-between"> <div className="mb-4 flex items-center">
<div className="flex-1">
<div className="flex items-center gap-x-2"> <div className="flex items-center gap-x-2">
<p className="text-xl font-medium text-mineshaft-100">Approval Policies</p> <p className="text-xl font-medium text-mineshaft-100">Access Policies</p>
</div> </div>
<p className="text-sm text-bunker-300">View and manage PAM access policies</p>
</div>
<Button <Button
variant="outline_bg" variant="outline_bg"
type="submit" type="submit"
@@ -51,6 +55,7 @@ export const PolicyTab = () => {
Create Policy Create Policy
</Button> </Button>
</div> </div>
<PoliciesTable handlePopUpOpen={handlePopUpOpen} /> <PoliciesTable handlePopUpOpen={handlePopUpOpen} />
<PolicyModal popUp={popUp} handlePopUpToggle={handlePopUpToggle} /> <PolicyModal popUp={popUp} handlePopUpToggle={handlePopUpToggle} />
<DeleteActionModal <DeleteActionModal
@@ -98,9 +98,7 @@ export const PoliciesTable = ({ handlePopUpOpen }: Props) => {
<Tr> <Tr>
<Th className="w-10" /> <Th className="w-10" />
<Th>Policy Name</Th> <Th>Policy Name</Th>
<Th>Max Approval Request TTL</Th> <Th>Max. Access Duration</Th>
<Th>Min Access Duration</Th>
<Th>Max Access Duration</Th>
<Th>Conditions</Th> <Th>Conditions</Th>
<Th className="w-5" /> <Th className="w-5" />
</Tr> </Tr>
@@ -117,7 +115,6 @@ export const PoliciesTable = ({ handlePopUpOpen }: Props) => {
{!isPoliciesLoading && {!isPoliciesLoading &&
policies.map((policy) => { policies.map((policy) => {
const isExpanded = expandedRows.has(policy.id); const isExpanded = expandedRows.has(policy.id);
const maxTtl = policy.maxRequestTtl ? policy.maxRequestTtl : "No limit";
const conditionsCount = policy.conditions.conditions.length; const conditionsCount = policy.conditions.conditions.length;
return ( return (
@@ -144,8 +141,6 @@ export const PoliciesTable = ({ handlePopUpOpen }: Props) => {
</IconButton> </IconButton>
</Td> </Td>
<Td>{policy.name}</Td> <Td>{policy.name}</Td>
<Td>{maxTtl}</Td>
<Td>{policy.constraints.constraints.accessDuration.min}</Td>
<Td>{policy.constraints.constraints.accessDuration.max}</Td> <Td>{policy.constraints.constraints.accessDuration.max}</Td>
<Td> <Td>
{conditionsCount} condition{conditionsCount !== 1 ? "s" : ""} {conditionsCount} condition{conditionsCount !== 1 ? "s" : ""}
@@ -202,7 +197,7 @@ export const PoliciesTable = ({ handlePopUpOpen }: Props) => {
{isExpanded && ( {isExpanded && (
<Tr className="bg-mineshaft-800"> <Tr className="bg-mineshaft-800">
<Td colSpan={7} className="p-0"> <Td colSpan={7} className="p-0">
<div className="flex max-h-80 w-full gap-2 gap-4 overflow-auto overflow-x-hidden p-4"> <div className="flex max-h-80 w-full gap-4 overflow-auto overflow-x-hidden p-4">
<div className="flex-1"> <div className="flex-1">
<div className="mb-2 text-sm font-medium text-mineshaft-300"> <div className="mb-2 text-sm font-medium text-mineshaft-300">
Approval Conditions Approval Conditions
@@ -15,7 +15,6 @@ import {
import { UsePopUpState } from "@app/hooks/usePopUp"; import { UsePopUpState } from "@app/hooks/usePopUp";
import { PolicyApprovalSteps } from "./PolicySteps/PolicyApprovalSteps"; import { PolicyApprovalSteps } from "./PolicySteps/PolicyApprovalSteps";
import { PolicyConstraintsStep } from "./PolicySteps/PolicyConstraintsStep";
import { PolicyDetailsStep } from "./PolicySteps/PolicyDetailsStep"; import { PolicyDetailsStep } from "./PolicySteps/PolicyDetailsStep";
import { PolicyReviewStep } from "./PolicySteps/PolicyReviewStep"; import { PolicyReviewStep } from "./PolicySteps/PolicyReviewStep";
import { PolicyFormSchema, TPolicyForm } from "./PolicySchema"; import { PolicyFormSchema, TPolicyForm } from "./PolicySchema";
@@ -26,9 +25,12 @@ type Props = {
}; };
const FORM_STEPS: { name: string; key: string; fields: (keyof TPolicyForm)[] }[] = [ const FORM_STEPS: { name: string; key: string; fields: (keyof TPolicyForm)[] }[] = [
{ name: "Details", key: "details", fields: ["name", "maxRequestTtl", "constraints"] }, {
{ name: "Conditions", key: "conditions", fields: ["conditions"] }, name: "Configuration",
{ name: "Approvals", key: "approvals", fields: ["steps"] }, key: "configuration",
fields: ["name", "constraints", "conditions"]
},
{ name: "Approval Sequence", key: "approvals", fields: ["steps"] },
{ name: "Review", key: "review", fields: [] } { name: "Review", key: "review", fields: [] }
]; ];
@@ -61,8 +63,7 @@ export const PolicyModal = ({ popUp, handlePopUpToggle }: Props) => {
approvers: [] approvers: []
} }
] ]
}, }
mode: "onChange"
}); });
const { handleSubmit, trigger, reset } = formMethods; const { handleSubmit, trigger, reset } = formMethods;
@@ -184,7 +185,8 @@ export const PolicyModal = ({ popUp, handlePopUpToggle }: Props) => {
return ( return (
<Modal isOpen={isOpen} onOpenChange={(open) => handlePopUpToggle("policy", open)}> <Modal isOpen={isOpen} onOpenChange={(open) => handlePopUpToggle("policy", open)}>
<ModalContent <ModalContent
title={policyData?.policyId ? "Edit Policy" : "Create Policy"} title={policyData?.policyId ? "Edit Access Policy" : "Create Access Policy"}
subTitle="Configure a policy dictating account access with constraints"
className="max-w-3xl" className="max-w-3xl"
> >
<FormProvider {...formMethods}> <FormProvider {...formMethods}>
@@ -215,9 +217,6 @@ export const PolicyModal = ({ popUp, handlePopUpToggle }: Props) => {
<Tab.Panel> <Tab.Panel>
<PolicyDetailsStep /> <PolicyDetailsStep />
</Tab.Panel> </Tab.Panel>
<Tab.Panel>
<PolicyConstraintsStep />
</Tab.Panel>
<Tab.Panel> <Tab.Panel>
<PolicyApprovalSteps /> <PolicyApprovalSteps />
</Tab.Panel> </Tab.Panel>
@@ -237,7 +236,8 @@ export const PolicyModal = ({ popUp, handlePopUpToggle }: Props) => {
isLoading={isCreating || isUpdating} isLoading={isCreating || isUpdating}
isDisabled={isCreating || isUpdating} isDisabled={isCreating || isUpdating}
> >
{policyData?.policyId ? "Update" : "Create"} {isFinalStep ? "" : "Next"} {isFinalStep && (policyData?.policyId ? "Update" : "Create")}
{!isFinalStep && "Next"}
</Button> </Button>
</div> </div>
</form> </form>
@@ -28,7 +28,13 @@ export const PolicyFormSchema = z.object({
).nullish(), ).nullish(),
conditions: z conditions: z
.object({ .object({
accountPaths: z.array(z.string().min(1)) accountPaths: z
.string()
.array()
.min(1, "Must have at least one account path")
.refine((val) => val.every((path) => path.length > 0), {
message: "All account paths must be non-empty"
})
}) })
.array() .array()
.min(1, "At least one condition is required"), .min(1, "At least one condition is required"),
@@ -40,7 +46,12 @@ export const PolicyFormSchema = z.object({
}), }),
steps: z steps: z
.object({ .object({
name: z.string().max(128).nullable().optional(), name: z
.string()
.max(128)
.nullable()
.optional()
.transform((name) => name || null),
requiredApprovals: z.number().min(1).max(100), requiredApprovals: z.number().min(1).max(100),
notifyApprovers: z.boolean().optional(), notifyApprovers: z.boolean().optional(),
approvers: z approvers: z
@@ -78,7 +78,7 @@ export const PolicyApprovalSteps = () => {
<div className="space-y-4"> <div className="space-y-4">
{stepFields.map((field, index) => ( {stepFields.map((field, index) => (
<div key={field.id} className="rounded border border-mineshaft-600 bg-mineshaft-800 p-4"> <div key={field.id} className="rounded border border-mineshaft-600 bg-mineshaft-700 p-4">
<div className="mb-3 flex items-center justify-between"> <div className="mb-3 flex items-center justify-between">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<span className="flex h-6 w-6 items-center justify-center rounded-full bg-primary/20 text-xs font-semibold text-primary"> <span className="flex h-6 w-6 items-center justify-center rounded-full bg-primary/20 text-xs font-semibold text-primary">
@@ -1,102 +0,0 @@
import { Controller, useFieldArray, useFormContext } from "react-hook-form";
import { faPlus, faTrash } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { Button, FormControl, IconButton, Input } from "@app/components/v2";
import { TPolicyForm } from "../PolicySchema";
export const PolicyConstraintsStep = () => {
const { control } = useFormContext<TPolicyForm>();
const {
fields: conditionFields,
append: appendCondition,
remove: removeCondition
} = useFieldArray({
control,
name: "conditions"
});
return (
<div className="space-y-6">
<div>
<div className="mb-3 flex items-center justify-between">
<div>
<p className="pb-0.5 text-sm font-medium text-mineshaft-200">Conditions</p>
<p className="text-xs text-mineshaft-400">
Define which resources and account paths this policy applies to
</p>
</div>
<Button
type="button"
variant="outline_bg"
size="xs"
leftIcon={<FontAwesomeIcon icon={faPlus} />}
onClick={() => appendCondition({ accountPaths: [] })}
>
Add Condition
</Button>
</div>
<div>
{conditionFields.map((field, index) => (
<div key={field.id}>
<div className="rounded border border-mineshaft-600 bg-mineshaft-800 p-4">
<div className="mb-3 flex items-center justify-between">
<span className="text-sm font-medium text-mineshaft-300">
Condition {index + 1}
</span>
{conditionFields.length > 1 && (
<IconButton
ariaLabel="Remove condition"
variant="plain"
size="xs"
onClick={() => removeCondition(index)}
>
<FontAwesomeIcon icon={faTrash} className="text-red-500" />
</IconButton>
)}
</div>
<div className="space-y-3">
<Controller
control={control}
name={`conditions.${index}.accountPaths`}
render={({ field: pathField, fieldState: { error } }) => (
<FormControl
label="Account Paths"
isError={Boolean(error)}
errorText={error?.message}
helperText="Matches any of the comma-separated account paths this condition applies to"
>
<Input
value={pathField.value.join(", ")}
onChange={(e) => {
const paths = e.target.value
.split(",")
.map((path) => path.trim())
.filter(Boolean);
pathField.onChange(paths);
}}
placeholder="e.g., /admin/**, /users/john, /**"
/>
</FormControl>
)}
/>
</div>
</div>
{index < conditionFields.length - 1 && (
<div className="flex items-center">
<div className="flex flex-col items-center">
<div className="h-3 w-px bg-mineshaft-500" />
<span className="px-2 text-xs font-medium text-mineshaft-400">OR</span>
<div className="h-3 w-px bg-mineshaft-500" />
</div>
</div>
)}
</div>
))}
</div>
</div>
</div>
);
};
@@ -9,13 +9,15 @@ export const PolicyDetailsStep = () => {
const { control } = useFormContext<TPolicyForm>(); const { control } = useFormContext<TPolicyForm>();
return ( return (
<div className="space-y-4"> <div className="flex flex-col gap-4">
<div className="flex w-full gap-4">
<Controller <Controller
control={control} control={control}
name="name" name="name"
render={({ field, fieldState: { error } }) => ( render={({ field, fieldState: { error } }) => (
<FormControl <FormControl
label="Policy Name" label="Policy Name"
className="mb-0 flex-1"
isRequired isRequired
isError={Boolean(error)} isError={Boolean(error)}
errorText={error?.message} errorText={error?.message}
@@ -24,60 +26,44 @@ export const PolicyDetailsStep = () => {
</FormControl> </FormControl>
)} )}
/> />
<Controller
control={control}
name="maxRequestTtl"
render={({ field, fieldState: { error } }) => (
<FormControl
isError={Boolean(error)}
errorText={error?.message}
label={<TtlFormLabel label="Max Approval Request TTL" />}
helperText="Maximum time-to-live for requests. Must be between 1 hour and 30 days. Leave empty for no limit."
>
<Input {...field} value={field.value ?? ""} placeholder="1h" />
</FormControl>
)}
/>
<div className="border-t border-mineshaft-600 pt-4">
<div className="mb-3">
<p className="pb-0.5 text-sm font-medium text-mineshaft-200">
PAM Account Access Duration TTL
</p>
<p className="text-xs text-mineshaft-400">
Set minimum and maximum duration (in seconds) for pam account access
</p>
</div>
<div className="grid grid-cols-2 gap-4">
<Controller
control={control}
name="constraints.accessDuration.min"
render={({ field, fieldState: { error } }) => (
<FormControl
label={<TtlFormLabel label="Minimum TTL" />}
isError={Boolean(error)}
errorText={error?.message}
helperText="Must be between 30s and 7 days"
>
<Input {...field} />
</FormControl>
)}
/>
<Controller <Controller
control={control} control={control}
name="constraints.accessDuration.max" name="constraints.accessDuration.max"
render={({ field, fieldState: { error } }) => ( render={({ field, fieldState: { error } }) => (
<FormControl <FormControl
label={<TtlFormLabel label="Maximum TTL" />} label={<TtlFormLabel label="Max. Access Duration" />}
className="mb-0"
isError={Boolean(error)} isError={Boolean(error)}
errorText={error?.message} errorText={error?.message}
helperText="Must be between 30s and 7 days"
> >
<Input {...field} /> <Input {...field} />
</FormControl> </FormControl>
)} )}
/> />
</div> </div>
</div>
<Controller
control={control}
name="conditions.0.accountPaths"
render={({ field: pathField, fieldState: { error } }) => (
<FormControl
isRequired
label="Account Paths"
isError={Boolean(error)}
errorText={error?.message}
helperText="Policy matches any of these comma-separated account paths"
>
<Input
value={pathField.value.join(",")}
onChange={(e) => {
pathField.onChange(e.target.value.split(",").map((path) => path.trim()));
}}
placeholder="e.g., /admin/**, /users/john, /**"
/>
</FormControl>
)}
/>
</div> </div>
); );
}; };
@@ -24,7 +24,7 @@ export const PolicyReviewStep = () => {
const { data: members = [] } = useGetWorkspaceUsers(projectId); const { data: members = [] } = useGetWorkspaceUsers(projectId);
const { data: groups = [] } = useListWorkspaceGroups(projectId); const { data: groups = [] } = useListWorkspaceGroups(projectId);
const { name, maxRequestTtl, conditions, constraints, steps } = watch(); const { name, conditions, constraints, steps } = watch();
const getApproverLabel = (approverId: string, approverType: ApproverType) => { const getApproverLabel = (approverId: string, approverType: ApproverType) => {
if (approverType === ApproverType.User) { if (approverType === ApproverType.User) {
@@ -45,61 +45,23 @@ export const PolicyReviewStep = () => {
<div className="space-y-6"> <div className="space-y-6">
<div> <div>
<div className="mb-3 border-b border-mineshaft-600 pb-2"> <div className="mb-3 border-b border-mineshaft-600 pb-2">
<h3 className="text-sm font-medium text-mineshaft-200">Policy Details</h3> <h3 className="text-sm font-medium text-mineshaft-200">Policy Configuration</h3>
</div> </div>
<div className="space-y-2"> <div className="grid grid-cols-2 gap-2">
<ReviewField label="Policy Name" value={name || "Not set"} /> <ReviewField label="Policy Name" value={name || "Not set"} />
<ReviewField label="Max Approval Request TTL" value={maxRequestTtl || "No Limit"} /> <ReviewField label="Max. Access Duration" value={constraints.accessDuration.max} />
<ReviewField
label="Account Paths"
value={
conditions[0].accountPaths.length ? conditions[0].accountPaths.join(",") : "Not set"
}
/>
</div> </div>
</div> </div>
<div> <div>
<div className="mb-3 border-b border-mineshaft-600 pb-2"> <div className="mb-3 border-b border-mineshaft-600 pb-2">
<h3 className="text-sm font-medium text-mineshaft-200"> <h3 className="text-sm font-medium text-mineshaft-200">Approval Sequence</h3>
PAM Account Access Duration TTL
</h3>
</div>
<div className="space-y-2">
<ReviewField label="Minimum Duration" value={constraints.accessDuration.min} />
<ReviewField label="Maximum Duration" value={constraints.accessDuration.max} />
</div>
</div>
<div>
<div className="mb-3 border-b border-mineshaft-600 pb-2">
<h3 className="text-sm font-medium text-mineshaft-200">
Conditions ({conditions.length})
</h3>
</div>
<div className="space-y-3">
{conditions.map((condition, index) => (
<div
key={`condition-${index + 1}`}
className="rounded border border-mineshaft-600 bg-mineshaft-800/50 p-3"
>
<div className="mb-2 text-xs font-medium text-mineshaft-300">
Condition {index + 1}
</div>
<div className="space-y-2">
<div>
<span className="text-xs text-mineshaft-400">Account Paths: </span>
<span className="text-xs text-mineshaft-200">
{condition.accountPaths.length > 0
? condition.accountPaths.join(", ")
: "None specified"}
</span>
</div>
</div>
</div>
))}
</div>
</div>
<div>
<div className="mb-3 border-b border-mineshaft-600 pb-2">
<h3 className="text-sm font-medium text-mineshaft-200">
Approval Workflow ({steps.length} step{steps.length !== 1 ? "s" : ""})
</h3>
</div> </div>
<div className="space-y-3"> <div className="space-y-3">
{steps.map((step, index) => { {steps.map((step, index) => {
@@ -109,7 +71,7 @@ export const PolicyReviewStep = () => {
return ( return (
<div <div
key={`step-${index + 1}`} key={`step-${index + 1}`}
className="rounded border border-mineshaft-600 bg-mineshaft-800/50 p-3" className="rounded border border-mineshaft-600 bg-mineshaft-700 p-3"
> >
<div className="mb-3 flex items-center justify-between"> <div className="mb-3 flex items-center justify-between">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
@@ -187,8 +149,8 @@ export const PolicyReviewStep = () => {
{/* Summary Notice */} {/* Summary Notice */}
<div className="rounded-md border border-primary/30 bg-primary/5 p-3"> <div className="rounded-md border border-primary/30 bg-primary/5 p-3">
<p className="text-xs text-mineshaft-300"> <p className="text-xs text-mineshaft-300">
Please review all the details above. Click &quot;Create&quot; to save this policy or Please review all the details above. Submit to save this policy or go back to make
&quot;Back&quot; to make changes. changes.
</p> </p>
</div> </div>
</div> </div>
@@ -1,4 +1,3 @@
export { PolicyApprovalSteps } from "./PolicyApprovalSteps"; export { PolicyApprovalSteps } from "./PolicyApprovalSteps";
export { PolicyConstraintsStep } from "./PolicyConstraintsStep";
export { PolicyDetailsStep } from "./PolicyDetailsStep"; export { PolicyDetailsStep } from "./PolicyDetailsStep";
export { PolicyReviewStep } from "./PolicyReviewStep"; export { PolicyReviewStep } from "./PolicyReviewStep";
@@ -180,9 +180,12 @@ export const RequestGrantTab = () => {
return ( return (
<div className="mb-6 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4"> <div className="mb-6 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
<div className="mb-4 flex items-center justify-between"> <div className="mb-4">
<div className="flex items-center gap-x-2">
<p className="text-xl font-medium text-mineshaft-100">Access Grants</p> <p className="text-xl font-medium text-mineshaft-100">Access Grants</p>
</div> </div>
<p className="text-sm text-bunker-300">View and revoke access grants to PAM accounts</p>
</div>
<div> <div>
<div className="flex gap-2"> <div className="flex gap-2">
<DropdownMenu> <DropdownMenu>
@@ -269,7 +272,7 @@ export const RequestGrantTab = () => {
<Tr> <Tr>
<Th>User</Th> <Th>User</Th>
<Th>Account Path</Th> <Th>Account Path</Th>
<Th>Duration</Th> <Th>Access Duration</Th>
<Th>Status</Th> <Th>Status</Th>
<Th>Granted</Th> <Th>Granted</Th>
<Th>Expires</Th> <Th>Expires</Th>
@@ -5,10 +5,10 @@ import {
faArrowDown, faArrowDown,
faArrowUp, faArrowUp,
faCheckCircle, faCheckCircle,
faClipboardCheck,
faFilter, faFilter,
faFolderPlus, faFolderPlus,
faMagnifyingGlass, faMagnifyingGlass,
faPen,
faPlus, faPlus,
faSearch faSearch
} from "@fortawesome/free-solid-svg-icons"; } from "@fortawesome/free-solid-svg-icons";
@@ -328,11 +328,11 @@ export const PamAccountsTable = ({ projectId }: Props) => {
</DropdownMenu> </DropdownMenu>
<Button <Button
variant="outline_bg" variant="outline_bg"
leftIcon={<FontAwesomeIcon icon={faPen} />} leftIcon={<FontAwesomeIcon icon={faClipboardCheck} />}
onClick={() => handlePopUpOpen("requestAccount")} onClick={() => handlePopUpOpen("requestAccount")}
className="h-10 transition-colors" className="h-10 transition-colors"
> >
Request Account Request Access
</Button> </Button>
<ProjectPermissionCan <ProjectPermissionCan
I={ProjectPermissionPamAccountActions.Create} I={ProjectPermissionPamAccountActions.Create}
@@ -111,6 +111,7 @@ const Content = ({ onOpenChange, account, accountPath }: Props) => {
control={control} control={control}
render={({ field, fieldState: { error } }) => ( render={({ field, fieldState: { error } }) => (
<FormControl <FormControl
isRequired
helperText="Account path including the account name. Supports glob patterns (e.g., /folder/**, /*/account-name)" helperText="Account path including the account name. Supports glob patterns (e.g., /folder/**, /*/account-name)"
errorText={error?.message} errorText={error?.message}
isError={Boolean(error?.message)} isError={Boolean(error?.message)}
@@ -126,7 +127,6 @@ const Content = ({ onOpenChange, account, accountPath }: Props) => {
render={({ field, fieldState: { error } }) => ( render={({ field, fieldState: { error } }) => (
<FormControl <FormControl
label={<TtlFormLabel label="Access Duration" />} label={<TtlFormLabel label="Access Duration" />}
helperText="Duration of access requested"
errorText={error?.message} errorText={error?.message}
isError={Boolean(error?.message)} isError={Boolean(error?.message)}
> >
@@ -139,7 +139,6 @@ const Content = ({ onOpenChange, account, accountPath }: Props) => {
control={control} control={control}
render={({ field, fieldState: { error } }) => ( render={({ field, fieldState: { error } }) => (
<FormControl <FormControl
helperText="Provide a reason for requesting access"
errorText={error?.message} errorText={error?.message}
isError={Boolean(error?.message)} isError={Boolean(error?.message)}
label="Justification" label="Justification"
@@ -178,7 +177,7 @@ export const PamRequestAccountAccessModal = (props: Props) => {
<ModalContent <ModalContent
className="max-w-2xl pb-2" className="max-w-2xl pb-2"
title="Request Account Access" title="Request Account Access"
subTitle="Request access to this account path" subTitle="Request access to an account path"
> >
<Content {...props} /> <Content {...props} />
</ModalContent> </ModalContent>