Compare commits

..

9 Commits

6 changed files with 279 additions and 274 deletions

View File

@ -1,29 +0,0 @@
import { Knex } from "knex";
import { TableName } from "../schemas";
export async function up(knex: Knex): Promise<void> {
await knex.raw(`
CREATE INDEX CONCURRENTLY IF NOT EXISTS ${TableName.IdentityAccessToken}_identityid_index
ON ${TableName.IdentityAccessToken} ("identityId")
`);
await knex.raw(`
CREATE INDEX CONCURRENTLY IF NOT EXISTS ${TableName.IdentityAccessToken}_identityuaclientsecretid_index
ON ${TableName.IdentityAccessToken} ("identityUAClientSecretId")
`);
}
export async function down(knex: Knex): Promise<void> {
await knex.raw(`
DROP INDEX IF EXISTS ${TableName.IdentityAccessToken}_identityid_index
`);
await knex.raw(`
DROP INDEX IF EXISTS ${TableName.IdentityAccessToken}_identityuaclientsecretid_index
`);
}
const config = { transaction: false };
export { config };

View File

@ -350,6 +350,12 @@ export const accessApprovalRequestServiceFactory = ({
const canBypass = !policy.bypassers.length || policy.bypassers.some((bypasser) => bypasser.userId === actorId);
const cannotBypassUnderSoftEnforcement = !(isSoftEnforcement && canBypass);
// Calculate break glass attempt before sequence checks
const isBreakGlassApprovalAttempt =
policy.enforcementLevel === EnforcementLevel.Soft &&
actorId === accessApprovalRequest.requestedByUserId &&
status === ApprovalStatus.APPROVED;
const isApprover = policy.approvers.find((approver) => approver.userId === actorId);
// If user is (not an approver OR cant self approve) AND can't bypass policy
if ((!isApprover || (!policy.allowedSelfApprovals && isSelfApproval)) && cannotBypassUnderSoftEnforcement) {
@ -409,15 +415,14 @@ export const accessApprovalRequestServiceFactory = ({
const isApproverOfTheSequence = policy.approvers.find(
(el) => el.sequence === presentSequence.step && el.userId === actorId
);
if (!isApproverOfTheSequence) throw new BadRequestError({ message: "You are not reviewer in this step" });
// Only throw if actor is not the approver and not bypassing
if (!isApproverOfTheSequence && !isBreakGlassApprovalAttempt) {
throw new BadRequestError({ message: "You are not a reviewer in this step" });
}
}
const reviewStatus = await accessApprovalRequestReviewerDAL.transaction(async (tx) => {
const isBreakGlassApprovalAttempt =
policy.enforcementLevel === EnforcementLevel.Soft &&
actorId === accessApprovalRequest.requestedByUserId &&
status === ApprovalStatus.APPROVED;
let reviewForThisActorProcessing: {
id: string;
requestId: string;

View File

@ -439,39 +439,35 @@ export const ReviewAccessRequestModal = ({
</div>
) : (
<>
{isSoftEnforcement &&
request.isRequestedByCurrentUser &&
!(request.isApprover && request.isSelfApproveAllowed) &&
canBypass && (
<div className="mt-2 flex flex-col space-y-2">
<Checkbox
onCheckedChange={(checked) => setBypassApproval(checked === true)}
isChecked={bypassApproval}
id="byPassApproval"
className={twMerge("mr-2", bypassApproval ? "border-red/30 bg-red/10" : "")}
{isSoftEnforcement && request.isRequestedByCurrentUser && canBypass && (
<div className="mt-2 flex flex-col space-y-2">
<Checkbox
onCheckedChange={(checked) => setBypassApproval(checked === true)}
isChecked={bypassApproval}
id="byPassApproval"
className={twMerge("mr-2", bypassApproval ? "!border-red/30 !bg-red/10" : "")}
>
<span className="text-xs text-red">
Approve without waiting for requirements to be met (bypass policy protection)
</span>
</Checkbox>
{bypassApproval && (
<FormControl
label="Reason for bypass"
className="mt-2"
isRequired
tooltipText="Enter a reason for bypassing the policy"
>
<span className="text-xs text-red">
Approve without waiting for requirements to be met (bypass policy
protection)
</span>
</Checkbox>
{bypassApproval && (
<FormControl
label="Reason for bypass"
className="mt-2"
isRequired
tooltipText="Enter a reason for bypassing the secret change policy"
>
<Input
value={bypassReason}
onChange={(e) => setBypassReason(e.currentTarget.value)}
placeholder="Enter reason for bypass (min 10 chars)"
leftIcon={<FontAwesomeIcon icon={faTriangleExclamation} />}
/>
</FormControl>
)}
</div>
)}
<Input
value={bypassReason}
onChange={(e) => setBypassReason(e.currentTarget.value)}
placeholder="Enter reason for bypass (min 10 chars)"
leftIcon={<FontAwesomeIcon icon={faTriangleExclamation} />}
/>
</FormControl>
)}
</div>
)}
<div className="space-x-2">
<Button
isLoading={isLoading === "approved"}

View File

@ -102,43 +102,73 @@ export const SecretApprovalRequestAction = ({
if (!hasMerged && status === "open") {
return (
<div className="flex w-full flex-col items-start justify-between py-4 transition-all">
<div className="flex items-center space-x-4 px-4">
<div
className={`flex items-center justify-center rounded-full ${isMergable ? "h-10 w-10 bg-green" : "h-11 w-11 bg-red-600"}`}
>
<FontAwesomeIcon
icon={isMergable ? faCheck : faXmark}
className={isMergable ? "text-lg text-black" : "text-2xl text-white"}
/>
<div className="flex w-full flex-col items-start justify-between py-4 text-mineshaft-100 transition-all">
<div className="flex w-full flex-col justify-between xl:flex-row xl:items-center">
<div className="mr-auto flex items-center space-x-4 px-4">
<div
className={`flex items-center justify-center rounded-full ${isMergable ? "h-8 w-8 bg-green" : "h-10 w-10 bg-red-600"}`}
>
<FontAwesomeIcon
icon={isMergable ? faCheck : faXmark}
className={isMergable ? "text-lg text-white" : "text-2xl text-white"}
/>
</div>
<span className="flex flex-col">
<p className={`text-md font-medium ${isMergable && "text-lg"}`}>
{isMergable ? "Good to merge" : "Merging is blocked"}
</p>
{!isMergable && (
<span className="inline-block text-xs text-mineshaft-300">
At least {approvals} approving review{`${approvals > 1 ? "s" : ""}`} required by
eligible reviewers.
{Boolean(statusChangeByEmail) && `. Reopened by ${statusChangeByEmail}`}
</span>
)}
</span>
</div>
<span className="flex flex-col">
<p className={`text-md font-medium ${isMergable && "text-lg"}`}>
{isMergable ? "Good to merge" : "Merging is blocked"}
</p>
{!isMergable && (
<span className="inline-block text-xs text-bunker-200">
At least {approvals} approving review{`${approvals > 1 ? "s" : ""}`} required by
eligible reviewers.
{Boolean(statusChangeByEmail) && `. Reopened by ${statusChangeByEmail}`}
</span>
<div className="mt-4 flex items-center justify-end space-x-2 px-4 xl:mt-0">
{canApprove || isSoftEnforcement ? (
<div className="flex items-center space-x-4">
<Button
onClick={() => handleSecretApprovalStatusChange("close")}
isLoading={isStatusChanging}
variant="outline_bg"
colorSchema="primary"
leftIcon={<FontAwesomeIcon icon={faClose} />}
className="hover:border-red/60 hover:bg-red/10"
>
Close request
</Button>
<Button
leftIcon={<FontAwesomeIcon icon={!canApprove ? faLandMineOn : faCheck} />}
isDisabled={
!(
(isMergable && canApprove) ||
(isSoftEnforcement && byPassApproval && isValidBypassReason(bypassReason))
)
}
isLoading={isMerging}
onClick={handleSecretApprovalRequestMerge}
colorSchema={isSoftEnforcement && !canApprove ? "danger" : "primary"}
variant="outline_bg"
>
Merge
</Button>
</div>
) : (
<div className="text-sm text-mineshaft-400">Only approvers can merge</div>
)}
</span>
</div>
</div>
{isSoftEnforcement && !isMergable && isBypasser && (
<div
className={`mt-4 w-full border-mineshaft-600 px-5 ${isMergable ? "border-t pb-2" : "border-y pb-4"}`}
>
<div className="mt-4 w-full border-t border-mineshaft-600 px-5">
<div className="mt-2 flex flex-col space-y-2 pt-2">
<Checkbox
onCheckedChange={(checked) => setByPassApproval(checked === true)}
isChecked={byPassApproval}
id="byPassApproval"
checkIndicatorBg="text-white"
className={twMerge(
"mr-2",
byPassApproval ? "border-red bg-red hover:bg-red-600" : ""
)}
className={twMerge("mr-2", byPassApproval ? "!border-red/30 !bg-red/10" : "")}
>
<span className="text-sm">
Merge without waiting for approval (bypass secret change policy)
@ -162,51 +192,18 @@ export const SecretApprovalRequestAction = ({
</div>
</div>
)}
<div className="mt-2 flex w-full items-center justify-end space-x-2 px-4">
{canApprove || isSoftEnforcement ? (
<div className="flex items-center space-x-4">
<Button
onClick={() => handleSecretApprovalStatusChange("close")}
isLoading={isStatusChanging}
variant="outline_bg"
colorSchema="primary"
leftIcon={<FontAwesomeIcon icon={faClose} />}
className="hover:border-red/60 hover:bg-red/10"
>
Close request
</Button>
<Button
leftIcon={<FontAwesomeIcon icon={!canApprove ? faLandMineOn : faCheck} />}
isDisabled={
!(
(isMergable && canApprove) ||
(isSoftEnforcement && byPassApproval && isValidBypassReason(bypassReason))
)
}
isLoading={isMerging}
onClick={handleSecretApprovalRequestMerge}
colorSchema={isSoftEnforcement && !canApprove ? "danger" : "primary"}
variant="solid"
>
Merge
</Button>
</div>
) : (
<div>Only approvers can merge</div>
)}
</div>
</div>
);
}
if (hasMerged && status === "close")
return (
<div className="flex w-full items-center justify-between rounded-md border border-primary/60 bg-primary/10">
<div className="flex items-start space-x-4 p-4">
<FontAwesomeIcon icon={faCheck} className="pt-1 text-2xl text-primary" />
<div className="flex w-full items-center justify-between rounded-md border border-green/60 bg-green/10">
<div className="flex items-start space-x-2 p-4">
<FontAwesomeIcon icon={faCheck} className="mt-0.5 text-xl text-green" />
<span className="flex flex-col">
Change request merged
<span className="inline-block text-xs text-bunker-200">
<span className="inline-block text-xs text-mineshaft-300">
Merged by {statusChangeByEmail}.
</span>
</span>
@ -215,26 +212,26 @@ export const SecretApprovalRequestAction = ({
);
return (
<div className="flex w-full items-center justify-between">
<div className="flex items-start space-x-4">
<FontAwesomeIcon icon={faUserLock} className="pt-1 text-2xl text-primary" />
<div className="flex w-full items-center justify-between rounded-md border border-yellow/60 bg-yellow/10">
<div className="flex items-start space-x-2 p-4">
<FontAwesomeIcon icon={faUserLock} className="mt-0.5 text-xl text-yellow" />
<span className="flex flex-col">
Secret approval has been closed
<span className="inline-block text-xs text-bunker-200">
<span className="inline-block text-xs text-mineshaft-300">
Closed by {statusChangeByEmail}
</span>
</span>
</div>
<div className="flex items-center space-x-6">
<Button
onClick={() => handleSecretApprovalStatusChange("open")}
isLoading={isStatusChanging}
variant="outline_bg"
leftIcon={<FontAwesomeIcon icon={faLockOpen} />}
>
Reopen request
</Button>
</div>
<Button
onClick={() => handleSecretApprovalStatusChange("open")}
isLoading={isStatusChanging}
variant="plain"
colorSchema="secondary"
className="mr-4 text-yellow/60 hover:text-yellow"
leftIcon={<FontAwesomeIcon icon={faLockOpen} />}
>
Reopen request
</Button>
</div>
);
};

View File

@ -3,11 +3,12 @@
/* eslint-disable no-nested-ternary */
import { useState } from "react";
import {
faCircleCheck,
faCircleXmark,
faExclamationTriangle,
faEye,
faEyeSlash,
faInfo,
faInfoCircle,
faKey
} from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
@ -29,14 +30,14 @@ export type Props = {
};
const generateItemTitle = (op: CommitType) => {
let text = { label: "", color: "" };
if (op === CommitType.CREATE) text = { label: "create", color: "#60DD00" };
else if (op === CommitType.UPDATE) text = { label: "change", color: "#F8EB30" };
else text = { label: "deletion", color: "#F83030" };
let text = { label: "", className: "" };
if (op === CommitType.CREATE) text = { label: "create", className: "text-green-600" };
else if (op === CommitType.UPDATE) text = { label: "change", className: "text-yellow-600" };
else text = { label: "deletion", className: "text-red-600" };
return (
<div className="text-md pb-2 font-medium">
Request for <span style={{ color: text.color }}>secret {text.label}</span>
Request for <span className={text.className}>secret {text.label}</span>
</div>
);
};
@ -68,15 +69,15 @@ export const SecretApprovalRequestChangeItem = ({
<div className="flex items-center px-1 py-1">
<div className="flex-grow">{generateItemTitle(op)}</div>
{!hasMerged && isStale && (
<div className="flex items-center">
<FontAwesomeIcon icon={faInfo} className="text-sm text-primary-600" />
<span className="ml-2 text-xs">Secret has been changed(stale)</span>
<div className="flex items-center text-mineshaft-300">
<FontAwesomeIcon icon={faInfoCircle} className="text-xs" />
<span className="ml-1 text-xs">Secret has been changed (stale)</span>
</div>
)}
{hasMerged && hasConflict && (
<div className="flex items-center space-x-2 text-sm text-bunker-300">
<div className="flex items-center space-x-1 text-xs text-bunker-300">
<Tooltip content="Merge Conflict">
<FontAwesomeIcon icon={faExclamationTriangle} className="text-red-700" />
<FontAwesomeIcon icon={faExclamationTriangle} className="text-xs text-red" />
</Tooltip>
<div>{generateConflictText(op)}</div>
</div>
@ -95,7 +96,7 @@ export const SecretApprovalRequestChangeItem = ({
</div>
<div className="mb-2">
<div className="text-sm font-medium text-mineshaft-300">Key</div>
<div className="text-sm">{secretVersion?.secretKey} </div>
<p className="max-w-lg break-words text-sm">{secretVersion?.secretKey}</p>
</div>
<div className="mb-2">
<div className="text-sm font-medium text-mineshaft-300">Value</div>
@ -147,7 +148,7 @@ export const SecretApprovalRequestChangeItem = ({
</div>
<div className="mb-2">
<div className="text-sm font-medium text-mineshaft-300">Comment</div>
<div className="max-h-[5rem] overflow-y-auto text-sm">
<div className="thin-scrollbar max-h-[5rem] max-w-[34rem] overflow-y-auto break-words text-sm xl:max-w-[28rem]">
{secretVersion?.secretComment || (
<span className="text-sm text-mineshaft-300">-</span>
)}{" "}
@ -186,15 +187,27 @@ export const SecretApprovalRequestChangeItem = ({
className="mr-0 flex items-center rounded-r-none border border-mineshaft-500"
>
<FontAwesomeIcon icon={faKey} size="xs" className="mr-1" />
<div>{el.key}</div>
<Tooltip
className="max-w-lg whitespace-normal break-words"
content={el.key}
>
<div className="max-w-[125px] overflow-hidden text-ellipsis whitespace-nowrap">
{el.key}
</div>
</Tooltip>
</Tag>
<Tag
size="xs"
className="flex items-center rounded-l-none border border-mineshaft-500 bg-mineshaft-900 pl-1"
>
<div className="max-w-[150px] overflow-hidden text-ellipsis whitespace-nowrap">
{el.value}
</div>
<Tooltip
className="max-w-lg whitespace-normal break-words"
content={el.value}
>
<div className="max-w-[125px] overflow-hidden text-ellipsis whitespace-nowrap">
{el.value}
</div>
</Tooltip>
</Tag>
</div>
))}
@ -215,13 +228,13 @@ export const SecretApprovalRequestChangeItem = ({
<div className="mb-4 flex flex-row justify-between">
<span className="text-md font-medium">New Secret</span>
<div className="rounded-full bg-green-600 px-2 pb-[0.14rem] pt-[0.2rem] text-xs font-medium">
<FontAwesomeIcon icon={faCircleXmark} className="pr-1 text-white" />
<FontAwesomeIcon icon={faCircleCheck} className="pr-1 text-white" />
New
</div>
</div>
<div className="mb-2">
<div className="text-sm font-medium text-mineshaft-300">Key</div>
<div className="text-sm">{newVersion?.secretKey} </div>
<div className="max-w-md break-words text-sm">{newVersion?.secretKey} </div>
</div>
<div className="mb-2">
<div className="text-sm font-medium text-mineshaft-300">Value</div>
@ -273,7 +286,7 @@ export const SecretApprovalRequestChangeItem = ({
</div>
<div className="mb-2">
<div className="text-sm font-medium text-mineshaft-300">Comment</div>
<div className="max-h-[5rem] overflow-y-auto text-sm">
<div className="thin-scrollbar max-h-[5rem] max-w-[34rem] overflow-y-auto break-words text-sm xl:max-w-[28rem]">
{newVersion?.secretComment || (
<span className="text-sm text-mineshaft-300">-</span>
)}{" "}
@ -281,15 +294,15 @@ export const SecretApprovalRequestChangeItem = ({
</div>
<div className="mb-2">
<div className="text-sm font-medium text-mineshaft-300">Tags</div>
<div className="flex flex-wrap gap-2">
<div className="flex flex-wrap gap-y-2">
{(newVersion?.tags?.length ?? 0) ? (
newVersion?.tags?.map(({ slug, id: tagId, color }) => (
<Tag
className="flex w-min items-center space-x-2"
className="flex w-min items-center space-x-1.5 border border-mineshaft-500 bg-mineshaft-800"
key={`${newVersion.id}-${tagId}`}
>
<div
className="h-3 w-3 rounded-full"
className="h-2.5 w-2.5 rounded-full"
style={{ backgroundColor: color || "#bec2c8" }}
/>
<div className="text-sm">{slug}</div>
@ -311,15 +324,27 @@ export const SecretApprovalRequestChangeItem = ({
className="mr-0 flex items-center rounded-r-none border border-mineshaft-500"
>
<FontAwesomeIcon icon={faKey} size="xs" className="mr-1" />
<div>{el.key}</div>
<Tooltip
className="max-w-lg whitespace-normal break-words"
content={el.key}
>
<div className="max-w-[125px] overflow-hidden text-ellipsis whitespace-nowrap">
{el.key}
</div>
</Tooltip>
</Tag>
<Tag
size="xs"
className="flex items-center rounded-l-none border border-mineshaft-500 bg-mineshaft-900 pl-1"
>
<div className="max-w-[150px] overflow-hidden text-ellipsis whitespace-nowrap">
{el.value}
</div>
<Tooltip
className="max-w-lg whitespace-normal break-words"
content={el.value}
>
<div className="max-w-[125px] overflow-hidden text-ellipsis whitespace-nowrap">
{el.value}
</div>
</Tooltip>
</Tag>
</div>
))}

View File

@ -3,12 +3,12 @@ import { Controller, useForm } from "react-hook-form";
import {
faAngleDown,
faArrowLeft,
faCheckCircle,
faCircle,
faBan,
faCheck,
faCodeBranch,
faComment,
faFolder,
faXmarkCircle
faHourglass
} from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { zodResolver } from "@hookform/resolvers/zod";
@ -26,6 +26,7 @@ import {
DropdownMenuTrigger,
EmptyState,
FormControl,
GenericFieldLabel,
IconButton,
TextArea,
Tooltip
@ -81,10 +82,10 @@ export const generateCommitText = (commits: { op: CommitType }[] = [], isReplica
const getReviewedStatusSymbol = (status?: ApprovalStatus) => {
if (status === ApprovalStatus.APPROVED)
return <FontAwesomeIcon icon={faCheckCircle} size="xs" style={{ color: "#15803d" }} />;
return <FontAwesomeIcon icon={faCheck} size="xs" className="text-green" />;
if (status === ApprovalStatus.REJECTED)
return <FontAwesomeIcon icon={faXmarkCircle} size="xs" style={{ color: "#b91c1c" }} />;
return <FontAwesomeIcon icon={faCircle} size="xs" style={{ color: "#c2410c" }} />;
return <FontAwesomeIcon icon={faBan} size="xs" className="text-red" />;
return <FontAwesomeIcon icon={faHourglass} size="xs" className="text-yellow" />;
};
type Props = {
@ -223,8 +224,8 @@ export const SecretApprovalRequestChanges = ({
const hasMerged = secretApprovalRequestDetails?.hasMerged;
return (
<div className="flex space-x-6">
<div className="flex-grow">
<div className="flex flex-col space-x-6 lg:flex-row">
<div className="flex-1 lg:max-w-[calc(100%-17rem)]">
<div className="sticky top-0 z-20 flex items-center space-x-4 bg-bunker-800 pb-6 pt-2">
<IconButton variant="outline_bg" ariaLabel="go-back" onClick={onGoBack}>
<FontAwesomeIcon icon={faArrowLeft} />
@ -242,17 +243,17 @@ export const SecretApprovalRequestChanges = ({
: secretApprovalRequestDetails.status}
</span>
</div>
<div className="flex-grow flex-col">
<div className="-mt-0.5 flex-grow flex-col">
<div className="text-xl">
{generateCommitText(
secretApprovalRequestDetails.commits,
secretApprovalRequestDetails.isReplicated
)}
</div>
<div className="flex items-center space-x-2 text-xs text-gray-400">
<span className="-mt-1 flex items-center space-x-2 text-xs text-gray-400">
By {secretApprovalRequestDetails?.committerUser?.firstName} (
{secretApprovalRequestDetails?.committerUser?.email})
</div>
</span>
</div>
{!hasMerged &&
secretApprovalRequestDetails.status === "open" &&
@ -262,7 +263,10 @@ export const SecretApprovalRequestChanges = ({
onOpenChange={(isOpen) => handlePopUpToggle("reviewChanges", isOpen)}
>
<DropdownMenuTrigger asChild>
<Button rightIcon={<FontAwesomeIcon className="ml-2" icon={faAngleDown} />}>
<Button
colorSchema="secondary"
rightIcon={<FontAwesomeIcon className="ml-2" icon={faAngleDown} />}
>
Review
</Button>
</DropdownMenuTrigger>
@ -279,82 +283,87 @@ export const SecretApprovalRequestChanges = ({
{...field}
placeholder="Leave a comment..."
reSize="none"
className="text-md mt-2 h-40 border border-mineshaft-600 bg-bunker-800"
className="text-md mt-2 h-40 border border-mineshaft-600 bg-mineshaft-800 placeholder:text-mineshaft-400"
/>
</FormControl>
)}
/>
<Controller
control={control}
name="status"
defaultValue={ApprovalStatus.APPROVED}
render={({ field, fieldState: { error } }) => (
<FormControl errorText={error?.message} isError={Boolean(error)}>
<RadioGroup
value={field.value}
onValueChange={field.onChange}
className="mb-4 space-y-2"
aria-label="Status"
<div className="flex justify-between">
<Controller
control={control}
name="status"
defaultValue={ApprovalStatus.APPROVED}
render={({ field, fieldState: { error } }) => (
<FormControl
className="mb-0"
errorText={error?.message}
isError={Boolean(error)}
>
<div className="flex items-center gap-2">
<RadioGroupItem
id="approve"
className="h-4 w-4 rounded-full border border-gray-300 text-primary focus:ring-2 focus:ring-mineshaft-500"
value={ApprovalStatus.APPROVED}
aria-labelledby="approve-label"
>
<RadioGroupIndicator className="flex h-full w-full items-center justify-center after:h-2 after:w-2 after:rounded-full after:bg-current" />
</RadioGroupItem>
<span
id="approve-label"
className="cursor-pointer"
onClick={() => field.onChange(ApprovalStatus.APPROVED)}
onKeyDown={(e) => {
if (e.key === "Enter" || e.key === " ") {
e.preventDefault();
field.onChange(ApprovalStatus.APPROVED);
}
}}
tabIndex={0}
role="button"
>
Approve
</span>
</div>
<div className="flex items-center gap-2">
<RadioGroupItem
id="reject"
className="h-4 w-4 rounded-full border border-gray-300 text-red focus:ring-2 focus:ring-mineshaft-500"
value={ApprovalStatus.REJECTED}
aria-labelledby="reject-label"
>
<RadioGroupIndicator className="flex h-full w-full items-center justify-center after:h-2 after:w-2 after:rounded-full after:bg-current" />
</RadioGroupItem>
<span
id="reject-label"
className="cursor-pointer"
onClick={() => field.onChange(ApprovalStatus.REJECTED)}
onKeyDown={(e) => {
if (e.key === "Enter" || e.key === " ") {
e.preventDefault();
field.onChange(ApprovalStatus.REJECTED);
}
}}
tabIndex={0}
role="button"
>
Reject
</span>
</div>
</RadioGroup>
</FormControl>
)}
/>
<div className="flex justify-end">
<RadioGroup
value={field.value}
onValueChange={field.onChange}
className="space-y-2"
aria-label="Status"
>
<div className="flex items-center gap-2">
<RadioGroupItem
id="approve"
className="h-4 w-4 rounded-full border border-gray-400 text-green focus:ring-2 focus:ring-mineshaft-500"
value={ApprovalStatus.APPROVED}
aria-labelledby="approve-label"
>
<RadioGroupIndicator className="flex h-full w-full items-center justify-center after:h-2 after:w-2 after:rounded-full after:bg-current" />
</RadioGroupItem>
<span
id="approve-label"
className="cursor-pointer"
onClick={() => field.onChange(ApprovalStatus.APPROVED)}
onKeyDown={(e) => {
if (e.key === "Enter" || e.key === " ") {
e.preventDefault();
field.onChange(ApprovalStatus.APPROVED);
}
}}
tabIndex={0}
role="button"
>
Approve
</span>
</div>
<div className="flex items-center gap-2">
<RadioGroupItem
id="reject"
className="h-4 w-4 rounded-full border border-gray-400 text-red focus:ring-2 focus:ring-mineshaft-500"
value={ApprovalStatus.REJECTED}
aria-labelledby="reject-label"
>
<RadioGroupIndicator className="flex h-full w-full items-center justify-center after:h-2 after:w-2 after:rounded-full after:bg-current" />
</RadioGroupItem>
<span
id="reject-label"
className="cursor-pointer"
onClick={() => field.onChange(ApprovalStatus.REJECTED)}
onKeyDown={(e) => {
if (e.key === "Enter" || e.key === " ") {
e.preventDefault();
field.onChange(ApprovalStatus.REJECTED);
}
}}
tabIndex={0}
role="button"
>
Reject
</span>
</div>
</RadioGroup>
</FormControl>
)}
/>
<Button
type="submit"
isLoading={isApproving || isRejecting || isSubmitting}
variant="outline_bg"
className="mt-auto h-min"
>
Submit Review
</Button>
@ -371,14 +380,14 @@ export const SecretApprovalRequestChanges = ({
<div className="text-sm text-bunker-300">
A secret import in
<p
className="mx-1 inline rounded bg-primary-600/40 text-primary-300"
className="mx-1 inline rounded bg-mineshaft-600/80 text-mineshaft-300"
style={{ padding: "2px 4px" }}
>
{secretApprovalRequestDetails?.environment}
</p>
<div className="mr-2 inline-flex w-min items-center rounded border border-mineshaft-500 pl-1 pr-2">
<p className="cursor-default border-r border-mineshaft-500 pr-1">
<FontAwesomeIcon icon={faFolder} className="text-primary" size="sm" />
<div className="mr-1 inline-flex w-min items-center rounded border border-mineshaft-500 pl-1.5 pr-2">
<p className="cursor-default border-r border-mineshaft-500 pr-1.5">
<FontAwesomeIcon icon={faFolder} className="text-yellow" size="sm" />
</p>
<Tooltip content={approvalSecretPath}>
<p
@ -391,14 +400,14 @@ export const SecretApprovalRequestChanges = ({
</div>
has pending changes to be accepted from its source at{" "}
<p
className="mx-1 inline rounded bg-primary-600/40 text-primary-300"
className="mx-1 inline rounded bg-mineshaft-600/80 text-mineshaft-300"
style={{ padding: "2px 4px" }}
>
{replicatedImport?.importEnv?.slug}
</p>
<div className="inline-flex w-min items-center rounded border border-mineshaft-500 pl-1 pr-2">
<p className="cursor-default border-r border-mineshaft-500 pr-1">
<FontAwesomeIcon icon={faFolder} className="text-primary" size="sm" />
<div className="mr-1 inline-flex w-min items-center rounded border border-mineshaft-500 pl-1.5 pr-2">
<p className="cursor-default border-r border-mineshaft-500 pr-1.5">
<FontAwesomeIcon icon={faFolder} className="text-yellow" size="sm" />
</p>
<Tooltip content={replicatedImport?.importPath}>
<p
@ -415,14 +424,14 @@ export const SecretApprovalRequestChanges = ({
<div className="text-sm text-bunker-300">
<p className="inline">Secret(s) in</p>
<p
className="mx-1 inline rounded bg-primary-600/40 text-primary-300"
className="mx-1 inline rounded bg-mineshaft-600/80 text-mineshaft-300"
style={{ padding: "2px 4px" }}
>
{secretApprovalRequestDetails?.environment}
</p>
<div className="mr-1 inline-flex w-min items-center rounded border border-mineshaft-500 pl-1 pr-2">
<p className="cursor-default border-r border-mineshaft-500 pr-1">
<FontAwesomeIcon icon={faFolder} className="text-primary" size="sm" />
<div className="mr-1 inline-flex w-min items-center rounded border border-mineshaft-500 pl-1.5 pr-2">
<p className="cursor-default border-r border-mineshaft-500 pr-1.5">
<FontAwesomeIcon icon={faFolder} className="text-yellow" size="sm" />
</p>
<Tooltip content={formatReservedPaths(secretApprovalRequestDetails.secretPath)}>
<p
@ -463,7 +472,7 @@ export const SecretApprovalRequestChanges = ({
const reviewer = reviewedUsers?.[requiredApprover.userId];
return (
<div
className="flex w-full flex-col rounded-md bg-mineshaft-800 p-4"
className="flex w-full flex-col rounded-md bg-mineshaft-800 p-4 text-sm text-mineshaft-100"
key={`required-approver-${requiredApprover.userId}`}
>
<div>
@ -477,14 +486,16 @@ export const SecretApprovalRequestChanges = ({
{reviewer?.status === ApprovalStatus.APPROVED ? "approved" : "rejected"}
</span>{" "}
the request on{" "}
{format(new Date(secretApprovalRequestDetails.createdAt), "PPpp zzz")}.
{format(
new Date(secretApprovalRequestDetails.createdAt),
"MM/dd/yyyy h:mm:ss aa"
)}
.
</div>
{reviewer?.comment && (
<FormControl label="Comment" className="mb-0 mt-4">
<TextArea value={reviewer.comment} isDisabled reSize="none">
{reviewer?.comment && reviewer.comment}
</TextArea>
</FormControl>
<GenericFieldLabel label="Comment" className="mt-2 max-w-4xl break-words">
{reviewer?.comment && reviewer.comment}
</GenericFieldLabel>
)}
</div>
);
@ -505,7 +516,7 @@ export const SecretApprovalRequestChanges = ({
/>
</div>
</div>
<div className="sticky top-0 w-1/5 cursor-default pt-4" style={{ minWidth: "240px" }}>
<div className="sticky top-0 z-[51] w-1/5 cursor-default pt-2" style={{ minWidth: "240px" }}>
<div className="text-sm text-bunker-300">Reviewers</div>
<div className="mt-2 flex flex-col space-y-2 text-sm">
{secretApprovalRequestDetails?.policy?.approvers
@ -526,17 +537,17 @@ export const SecretApprovalRequestChanges = ({
requiredApprover.lastName || ""
}`}
>
<span>{requiredApprover?.email} </span>
<span>{requiredApprover?.email}</span>
</Tooltip>
<span className="text-red">*</span>
</div>
<div>
{reviewer?.comment && (
<Tooltip content={reviewer.comment}>
<Tooltip className="max-w-lg break-words" content={reviewer.comment}>
<FontAwesomeIcon
icon={faComment}
size="xs"
className="mr-1 text-mineshaft-300"
className="mr-1.5 text-mineshaft-300"
/>
</Tooltip>
)}