improvements: revise ui elements

This commit is contained in:
Scott Wilson
2025-07-21 12:09:44 -07:00
parent 51d4444c77
commit f29593eb60
10 changed files with 280 additions and 370 deletions
@@ -20,7 +20,7 @@ export const getDefaultOnPremFeatures = (): TFeatureSet => ({
identitiesUsed: 0, identitiesUsed: 0,
dynamicSecret: false, dynamicSecret: false,
secretVersioning: true, secretVersioning: true,
pitRecovery: true, pitRecovery: false,
ipAllowlisting: false, ipAllowlisting: false,
rbac: false, rbac: false,
githubOrgSync: false, githubOrgSync: false,
@@ -40,7 +40,7 @@ export const getDefaultOnPremFeatures = (): TFeatureSet => ({
status: null, status: null,
trial_end: null, trial_end: null,
has_used_trial: true, has_used_trial: true,
secretApproval: true, secretApproval: false,
secretRotation: false, secretRotation: false,
caCrl: false, caCrl: false,
instanceUserManagement: false, instanceUserManagement: false,
@@ -41,7 +41,8 @@ export const CopyButton = ({
variant={variant} variant={variant}
className={twMerge("group relative", color)} className={twMerge("group relative", color)}
size={size} size={size}
onClick={() => { onClick={(e) => {
e.stopPropagation();
handleCopyText(); handleCopyText();
}} }}
> >
+5 -1
View File
@@ -1,6 +1,10 @@
import { useCallback } from "react"; import { useCallback } from "react";
import { useBlocker } from "@tanstack/react-router"; import { useBlocker } from "@tanstack/react-router";
import { BatchContext, useBatchModeActions } from "@app/pages/secret-manager/SecretDashboardPage/SecretMainPage.store";
import {
BatchContext,
useBatchModeActions
} from "@app/pages/secret-manager/SecretDashboardPage/SecretMainPage.store";
type TNavigationBlockerReturn = { type TNavigationBlockerReturn = {
isBlocked: boolean; isBlocked: boolean;
@@ -1,9 +1,11 @@
/* eslint-disable no-nested-ternary */ /* eslint-disable no-nested-ternary */
import { useCallback, useRef, useState } from "react"; import { useCallback, useRef, useState } from "react";
import { faChevronDown, faChevronUp } from "@fortawesome/free-solid-svg-icons"; import { faChevronDown, faChevronUp, faTrash } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { twMerge } from "tailwind-merge"; import { twMerge } from "tailwind-merge";
import { IconButton, Tooltip } from "@app/components/v2";
export interface Version { export interface Version {
id?: string; id?: string;
version: number; version: number;
@@ -33,6 +35,7 @@ interface SecretVersionDiffViewProps {
showHeader?: boolean; showHeader?: boolean;
customHeader?: JSX.Element; customHeader?: JSX.Element;
excludedFieldsHighlight?: string[]; excludedFieldsHighlight?: string[];
onDiscard?: VoidFunction;
} }
const isObject = (obj: JsonValue): obj is JsonObject => { const isObject = (obj: JsonValue): obj is JsonObject => {
@@ -409,7 +412,6 @@ const renderJsonWithDiffs = (
<div> <div>
{indent} {indent}
<span>{"}"}</span> <span>{"}"}</span>
{comma}
</div> </div>
</div> </div>
</div> </div>
@@ -465,7 +467,9 @@ const formatDeletedJson = (json: JsonValue): JSX.Element => {
const cleanVersionForComparison = (version: Version): JsonValue => { const cleanVersionForComparison = (version: Version): JsonValue => {
const { id, version: versionNumber, ...cleanVersion } = version; const { id, version: versionNumber, ...cleanVersion } = version;
return Object.fromEntries(Object.entries(cleanVersion).filter((entry) => Boolean(entry[1]))); return Object.fromEntries(
Object.entries(cleanVersion).filter((entry) => typeof entry[1] !== "undefined")
);
}; };
export const SecretVersionDiffView = ({ export const SecretVersionDiffView = ({
@@ -474,9 +478,9 @@ export const SecretVersionDiffView = ({
onToggleCollapse, onToggleCollapse,
showHeader = true, showHeader = true,
customHeader, customHeader,
excludedFieldsHighlight = ["metadata", "tags"] excludedFieldsHighlight = ["metadata", "tags"],
onDiscard
}: SecretVersionDiffViewProps) => { }: SecretVersionDiffViewProps) => {
console.log("item", item);
const oldContainerRef = useRef<HTMLDivElement>(null); const oldContainerRef = useRef<HTMLDivElement>(null);
const newContainerRef = useRef<HTMLDivElement>(null); const newContainerRef = useRef<HTMLDivElement>(null);
const [internalCollapsed, setInternalCollapsed] = useState(isCollapsed); const [internalCollapsed, setInternalCollapsed] = useState(isCollapsed);
@@ -610,6 +614,20 @@ export const SecretVersionDiffView = ({
<p className={twMerge(textStyle, "truncate")}>{key}</p> <p className={twMerge(textStyle, "truncate")}>{key}</p>
{changeBadge} {changeBadge}
</div> </div>
{onDiscard && (
<Tooltip side="left" content="Discard change">
<IconButton
ariaLabel="discard-change"
variant="plain"
colorSchema="danger"
size="sm"
className="ml-2"
onClick={onDiscard}
>
<FontAwesomeIcon icon={faTrash} />
</IconButton>
</Tooltip>
)}
<FontAwesomeIcon <FontAwesomeIcon
icon={collapsed ? faChevronDown : faChevronUp} icon={collapsed ? faChevronDown : faChevronUp}
className="ml-2 text-gray-400" className="ml-2 text-gray-400"
@@ -1,10 +1,9 @@
/* eslint-disable jsx-a11y/label-has-associated-control */ /* eslint-disable jsx-a11y/label-has-associated-control */
import React, { useState } from "react"; import React, { useCallback, useState } from "react";
import { faCodeCommit, faEye, faFolder, faKey, faTrash } from "@fortawesome/free-solid-svg-icons"; import { faCodeCommit, faEye, faFolder, faKey, faTrash } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { Badge, Button, Input, Modal, ModalContent } from "@app/components/v2"; import { Badge, Button, Input, Modal, ModalContent } from "@app/components/v2";
import { useToggle } from "@app/hooks";
import { PendingAction } from "@app/hooks/api/secretFolders/types"; import { PendingAction } from "@app/hooks/api/secretFolders/types";
import { SecretVersionDiffView } from "@app/pages/secret-manager/CommitDetailsPage/components/SecretVersionDiffView"; import { SecretVersionDiffView } from "@app/pages/secret-manager/CommitDetailsPage/components/SecretVersionDiffView";
@@ -23,356 +22,242 @@ interface CommitFormProps {
secretPath: string; secretPath: string;
} }
interface ChangeTableProps { interface ResourceChangeProps {
change: PendingChange; change: PendingChange;
environment: string; environment: string;
workspaceId: string; workspaceId: string;
secretPath: string; secretPath: string;
} }
const TagsList: React.FC<{ tags?: { id: string; slug: string }[]; className?: string }> = ({ type RenderResourceProps = {
tags, onDiscard: () => void;
className = "" change: PendingChange;
}) => { };
if (!tags || tags.length === 0) {
return <span className={`italic text-mineshaft-400 ${className}`}>(no tags)</span>; const RenderSecretChanges = ({ onDiscard, change }: RenderResourceProps) => {
if (change.resourceType !== "secret") return null;
if (change.type === PendingAction.Create) {
return (
<SecretVersionDiffView
onDiscard={onDiscard}
item={{
secretKey: change.secretKey,
isAdded: true,
type: "secret",
id: change.id,
versions: [
{
version: 1, // placeholder, not used
secretKey: change.secretKey,
secretValue: change.secretValue,
tags: change.tags,
secretMetadata: change.secretMetadata,
skipMultilineEncoding: change.skipMultilineEncoding,
comment: change.secretComment
}
]
}}
/>
);
} }
return ( if (change.type === PendingAction.Update) {
<div className={`flex flex-wrap gap-1 ${className}`}> const { existingSecret } = change;
{tags.map((tag) => (
<Badge key={tag.id} variant="success" className="text-xs">
{tag.slug}
</Badge>
))}
</div>
);
};
const MetadataList: React.FC<{ const hasKeyChange = change.newSecretName && change.secretKey !== change.newSecretName;
metadata?: { key: string; value: string }[]; const hasValueChange = change.secretValue !== change.originalValue;
className?: string; const hasCommentChange = change.secretComment !== change.originalComment;
}> = ({ metadata, className = "" }) => { const hasMultilineChange =
if (!metadata || metadata.length === 0) { change.skipMultilineEncoding !== change.originalSkipMultilineEncoding;
return <span className={`italic text-mineshaft-400 ${className}`}>(no metadata)</span>; const hasTagsChange = JSON.stringify(change.tags) !== JSON.stringify(change.originalTags);
const hasMetadataChange =
JSON.stringify(change.secretMetadata) !== JSON.stringify(change.originalSecretMetadata);
const hasChanges = [
hasKeyChange,
hasValueChange,
hasCommentChange,
hasMultilineChange,
hasTagsChange,
hasMetadataChange
].some(Boolean);
if (!hasChanges) return null;
return (
<SecretVersionDiffView
onDiscard={onDiscard}
item={{
secretKey: change.secretKey,
isUpdated: true,
type: "secret",
id: change.id,
versions: [
{
version: 1, // placeholder, not used
secretKey: change.newSecretName ? existingSecret.key : undefined,
secretValue: change.secretValue ? existingSecret.value : "",
tags: change.tags ? (existingSecret.tags?.map((tag) => tag.slug) ?? []) : undefined,
secretMetadata: change.secretMetadata ? existingSecret.secretMetadata : undefined,
skipMultilineEncoding:
typeof change.skipMultilineEncoding === "boolean"
? existingSecret.skipMultilineEncoding
: undefined,
comment: change.secretComment !== undefined ? existingSecret.comment : undefined
},
{
version: 2, // placeholder, not used
secretKey: change.newSecretName,
secretValue: change.secretValue,
tags: change.tags?.map((tag) => tag.slug),
secretMetadata: change.secretMetadata,
skipMultilineEncoding: change.skipMultilineEncoding,
comment: change.secretComment
}
]
}}
/>
);
} }
return ( if (change.type === PendingAction.Delete) {
<div className={`space-y-1 ${className}`}> const { secretKey, secretValue } = change;
{metadata.map((item) => ( return (
<div key={item.key} className="flex items-center gap-2 text-xs"> <SecretVersionDiffView
<span className="font-medium text-mineshaft-300">{item.key}:</span> onDiscard={onDiscard}
<span className="font-mono text-mineshaft-100">{item.value}</span> item={{
</div> secretKey: change.secretKey,
))} isDeleted: true,
</div> type: "secret",
); id: change.id,
versions: [
{
version: 1, // placeholder, not used
secretKey,
secretValue
}
]
}}
/>
);
}
return null;
}; };
const ComparisonTableRow: React.FC<{ const RenderFolderChanges = ({ onDiscard, change }: RenderResourceProps) => {
label: string; if (change.resourceType !== "folder") return null;
previousValue: React.ReactNode;
newValue: React.ReactNode;
hideIfSame?: boolean;
}> = ({ label, previousValue, newValue, hideIfSame = false }) => {
const isSame = hideIfSame && String(previousValue) === String(newValue);
if (isSame) return null; if (change.type === PendingAction.Create) {
return (
<SecretVersionDiffView
onDiscard={onDiscard}
item={{
folderName: change.folderName,
isAdded: true,
type: "folder",
id: change.id,
versions: [
{
version: 1,
name: change.folderName,
description: change.description
}
]
}}
/>
);
}
return ( if (change.type === PendingAction.Update) {
<tr className="border-b border-mineshaft-700 last:border-b-0"> const hasNameChange = change.folderName !== change.originalFolderName;
<td className="w-[12%] border-r border-mineshaft-600 px-4 py-3 align-top font-medium text-mineshaft-300"> const hasDescriptionChange = change.description !== change.originalDescription;
{label}
</td> const hasChanges = [hasNameChange, hasDescriptionChange].some(Boolean);
<td className="w-1/2 border-r border-mineshaft-600 px-4 py-3 align-top">
<div className="text-red-400 opacity-80">{previousValue}</div> if (!hasChanges) return null;
</td>
<td className="w-1/2 py-3 pr-4 align-top"> return (
<div className="px-4 text-green-400">{newValue}</div> <SecretVersionDiffView
</td> onDiscard={onDiscard}
</tr> item={{
); folderName: change.folderName,
isUpdated: true,
type: "folder",
id: change.id,
versions: [
{
version: 1, // placeholder, not used
name: change.folderName ?? change.originalFolderName,
description: change.description ? change.originalDescription : undefined
},
{
version: 2, // placeholder, not used
name: change.folderName ?? change.originalFolderName,
description: change.description
}
]
}}
/>
);
}
if (change.type === PendingAction.Delete) {
return (
<SecretVersionDiffView
onDiscard={onDiscard}
item={{
folderName: change.folderName,
isDeleted: true,
type: "folder",
id: change.id,
versions: [
{
version: 1,
name: change.folderName
}
]
}}
/>
);
}
return null;
}; };
const ChangeTable: React.FC<ChangeTableProps> = ({ const ResourceChange: React.FC<ResourceChangeProps> = ({
change, change,
environment, environment,
workspaceId, workspaceId,
secretPath secretPath
}) => { }) => {
const [isOpen, setIsOpen] = useToggle(true);
const getChangeBadge = (type: PendingChange["type"]) => {
switch (type) {
case PendingAction.Create:
return <Badge variant="success">Created</Badge>;
case PendingAction.Update:
return <Badge variant="primary">Updated</Badge>;
case PendingAction.Delete:
return <Badge variant="danger">Deleted</Badge>;
default:
return null;
}
};
const renderSecretChanges = () => {
if (change.resourceType !== "secret") return null;
if (change.type === PendingAction.Create) {
return (
<div className="overflow-hidden rounded-md bg-mineshaft-900/80">
<table className="w-full text-sm">
<tbody>
<tr className="border-b border-mineshaft-700">
<td className="w-24 py-3 pl-4 font-medium text-mineshaft-300">Key:</td>
<td className="px-3 py-3 font-mono text-mineshaft-100" colSpan={2}>
{change.secretKey}
</td>
</tr>
<tr className="border-b border-mineshaft-700">
<td className="w-24 py-3 pl-4 font-medium text-mineshaft-300">Value:</td>
<td className="px-3 py-3" colSpan={2}>
<div className="max-w-md break-all px-2 py-1 font-mono text-xs text-mineshaft-100">
{change.secretValue || (
<span className="italic text-mineshaft-400">(empty)</span>
)}
</div>
</td>
</tr>
{change.secretComment !== undefined && change.secretComment !== "" && (
<tr className="border-b border-mineshaft-700">
<td className="w-24 py-3 pl-4 font-medium text-mineshaft-300">Comment:</td>
<td className="px-3 py-3 text-mineshaft-100" colSpan={2}>
{change.secretComment}
</td>
</tr>
)}
{change.tags && change.tags.length > 0 && (
<tr className="border-b border-mineshaft-700">
<td className="w-24 py-3 pl-4 font-medium text-mineshaft-300">Tags:</td>
<td className="px-3 py-3" colSpan={2}>
<TagsList tags={change.tags} />
</td>
</tr>
)}
{change.secretMetadata && change.secretMetadata.length > 0 && (
<tr className="border-b border-mineshaft-700 last:border-b-0">
<td className="w-24 py-3 pl-4 font-medium text-mineshaft-300">Metadata:</td>
<td className="px-3 py-3" colSpan={2}>
<MetadataList metadata={change.secretMetadata} />
</td>
</tr>
)}
</tbody>
</table>
</div>
);
}
if (change.type === PendingAction.Update) {
const { existingSecret } = change;
const hasKeyChange = change.newSecretName && change.secretKey !== change.newSecretName;
const hasValueChange = change.secretValue !== change.originalValue;
const hasCommentChange = change.secretComment !== change.originalComment;
const hasMultilineChange =
change.skipMultilineEncoding !== change.originalSkipMultilineEncoding;
const hasTagsChange = JSON.stringify(change.tags) !== JSON.stringify(change.originalTags);
const hasMetadataChange =
JSON.stringify(change.secretMetadata) !== JSON.stringify(change.originalSecretMetadata);
const hasChanges = [
hasKeyChange,
hasValueChange,
hasCommentChange,
hasMultilineChange,
hasTagsChange,
hasMetadataChange
].some(Boolean);
if (!hasChanges) return null;
return (
<SecretVersionDiffView
item={{
secretKey: change.secretKey,
isUpdated: true,
type: "secret",
id: change.id,
versions: [
{
version: 1,
secretKey: change.newSecretName ? existingSecret.key : undefined,
secretValue: change.secretValue ? existingSecret.value : undefined,
tags: change.tags ? existingSecret.tags : undefined,
secretMetadata: change.secretMetadata ? existingSecret.secretMetadata : undefined,
skipMultilineEncoding:
typeof change.skipMultilineEncoding === "boolean"
? existingSecret.skipMultilineEncoding
: undefined,
comment: change.secretComment !== undefined ? existingSecret.comment : undefined
},
{
version: 2,
secretKey: change.newSecretName,
secretValue: change.secretValue,
tags: change.tags,
secretMetadata: change.secretMetadata,
skipMultilineEncoding: change.skipMultilineEncoding,
comment: change.secretComment
}
]
}}
/>
);
}
if (change.type === PendingAction.Delete) {
const { secretKey, secretValue } = change;
return (
<SecretVersionDiffView
item={{
secretKey: change.secretKey,
isDeleted: true,
type: "secret",
id: change.id,
versions: [
{
version: 1,
secretKey,
secretValue
}
]
}}
/>
);
}
return null;
};
const renderFolderChanges = () => {
if (change.resourceType !== "folder") return null;
if (change.type === PendingAction.Create) {
return (
<table className="w-full text-sm">
<tbody>
<tr className="border-b border-mineshaft-700">
<td className="w-24 py-3 pl-4 font-medium text-mineshaft-300">Name:</td>
<td className="px-3 py-3 font-mono text-mineshaft-100" colSpan={2}>
{change.folderName}
</td>
</tr>
{change.description !== undefined && change.description !== "" && (
<tr className="border-b border-mineshaft-700 last:border-b-0">
<td className="w-24 py-3 pl-4 font-medium text-mineshaft-300">Description:</td>
<td className="px-3 py-3 text-mineshaft-100" colSpan={2}>
{change.description}
</td>
</tr>
)}
</tbody>
</table>
);
}
if (change.type === PendingAction.Update) {
const hasNameChange = change.folderName !== change.originalFolderName;
const hasDescriptionChange = change.description !== change.originalDescription;
const hasChanges = [hasNameChange, hasDescriptionChange].some(Boolean);
if (!hasChanges) return null;
return (
<table className="w-full text-sm">
<tbody>
{hasNameChange && (
<ComparisonTableRow
label="Name"
previousValue={<span className="font-mono">{change.originalFolderName}</span>}
newValue={<span className="font-mono">{change.folderName}</span>}
/>
)}
{hasDescriptionChange && (
<ComparisonTableRow
label="Description"
previousValue={
change.originalDescription || <span className="italic">(empty)</span>
}
newValue={change.description || <span className="italic">(empty)</span>}
/>
)}
</tbody>
</table>
);
}
if (change.type === PendingAction.Delete) {
return (
<table className="w-full text-sm">
<tbody>
<tr className="border-b border-mineshaft-700">
<td className="w-24 py-3 pl-4 font-medium text-red-400">Name:</td>
<td className="px-3 py-3 font-mono text-red-400 line-through" colSpan={2}>
{change.folderName}
</td>
</tr>
</tbody>
</table>
);
}
return null;
};
const getChangeName = () => {
if (change.resourceType === "secret") {
return change.type === PendingAction.Update
? change.newSecretName || change.secretKey
: change.secretKey;
}
if (change.resourceType === "folder") {
return change.type === PendingAction.Update ? change.originalFolderName : change.folderName;
}
return "Unknown";
};
const { removePendingChange } = useBatchModeActions(); const { removePendingChange } = useBatchModeActions();
const handleDeletePending = (changeType: string, id: string) => { const handleDeletePending = useCallback(
removePendingChange(id, changeType, { (changeType: string, id: string) => {
workspaceId, removePendingChange(id, changeType, {
environment, workspaceId,
secretPath environment,
}); secretPath
}; });
},
[change.resourceType, change.id]
);
return change.resourceType === "secret" ? renderSecretChanges() : renderFolderChanges(); return change.resourceType === "secret" ? (
<RenderSecretChanges
// return ( key={change.id}
// <div className="py-2 shadow-sm"> change={change}
// <div className="flex items-center justify-between"> onDiscard={() => handleDeletePending(change.resourceType, change.id)}
// <div className="flex items-center gap-3"> />
// <span className="font-medium text-mineshaft-100">{getChangeName()}</span> ) : (
// {getChangeBadge(change.type)} <RenderFolderChanges
// </div> key={change.id}
// <Tooltip content="Discard change"> change={change}
// <IconButton onDiscard={() => handleDeletePending(change.resourceType, change.id)}
// ariaLabel="delete-change" />
// variant="plain" );
// colorSchema="danger"
// size="sm"
// onClick={() => handleDeletePending(change.resourceType, change.id)}
// >
// <FontAwesomeIcon icon={faTrash} />
// </IconButton>
// </Tooltip>
// </div>
// {change.resourceType === "secret" ? renderSecretChanges() : renderFolderChanges()}
// </div>
// );
}; };
export const CommitForm: React.FC<CommitFormProps> = ({ export const CommitForm: React.FC<CommitFormProps> = ({
@@ -460,12 +345,12 @@ export const CommitForm: React.FC<CommitFormProps> = ({
</div> </div>
} }
subTitle={"Write a commit message and review the changes you&apos;re about to commit."} subTitle={"Write a commit message and review the changes you&apos;re about to commit."}
className="max-h-[90vh] max-w-5xl" className="max-h-[90vh] max-w-[95%] md:max-w-7xl"
> >
<div className="space-y-6"> <div className="space-y-6">
{/* Changes List */} {/* Changes List */}
<div className="space-y-6"> <div className="space-y-6">
<div className="max-h-[50vh] space-y-4 overflow-y-auto rounded-md border border-mineshaft-600 bg-bunker-800 p-4 shadow-inner"> <div className="max-h-[50vh] space-y-4 overflow-y-auto">
{/* Folder Changes */} {/* Folder Changes */}
{pendingChanges.folders.length > 0 && ( {pendingChanges.folders.length > 0 && (
<div> <div>
@@ -475,7 +360,7 @@ export const CommitForm: React.FC<CommitFormProps> = ({
</h4> </h4>
<div> <div>
{pendingChanges.folders.map((change) => ( {pendingChanges.folders.map((change) => (
<ChangeTable <ResourceChange
key={change.id} key={change.id}
change={change} change={change}
environment={environment} environment={environment}
@@ -490,13 +375,13 @@ export const CommitForm: React.FC<CommitFormProps> = ({
{/* Secret Changes */} {/* Secret Changes */}
{pendingChanges.secrets.length > 0 && ( {pendingChanges.secrets.length > 0 && (
<div> <div>
<h4 className="mb-2 flex items-center gap-2 px-2 text-sm text-mineshaft-300"> <h4 className="mb-4 flex items-center gap-2 border-b border-mineshaft-700 pb-2 text-sm font-semibold text-mineshaft-200">
<FontAwesomeIcon icon={faKey} className="mr-1 text-mineshaft-300" /> <FontAwesomeIcon icon={faKey} className="mr-1 text-mineshaft-300" />
Secrets ({pendingChanges.secrets.length}) Secrets ({pendingChanges.secrets.length})
</h4> </h4>
<div> <div>
{pendingChanges.secrets.map((change) => ( {pendingChanges.secrets.map((change) => (
<ChangeTable <ResourceChange
key={change.id} key={change.id}
change={change} change={change}
environment={environment} environment={environment}
@@ -1,10 +1,5 @@
import { subject } from "@casl/ability"; import { subject } from "@casl/ability";
import { import { faEdit, faFingerprint, faTrash, faWarning } from "@fortawesome/free-solid-svg-icons";
faClose,
faFingerprint,
faPencilSquare,
faWarning
} from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { createNotification } from "@app/components/notifications"; import { createNotification } from "@app/components/notifications";
@@ -187,7 +182,7 @@ export const DynamicSecretListView = ({
</Tooltip> </Tooltip>
)} )}
</div> </div>
<div className="flex items-center space-x-4 border-l border-mineshaft-600 px-3 py-3"> <div className="flex w-16 items-center justify-between border-l border-mineshaft-600 px-3 py-3">
<ProjectPermissionCan <ProjectPermissionCan
I={ProjectPermissionDynamicSecretActions.EditRootCredential} I={ProjectPermissionDynamicSecretActions.EditRootCredential}
a={subject(ProjectPermissionSub.DynamicSecrets, { a={subject(ProjectPermissionSub.DynamicSecrets, {
@@ -210,7 +205,7 @@ export const DynamicSecretListView = ({
}} }}
isDisabled={!isAllowed || isRevoking} isDisabled={!isAllowed || isRevoking}
> >
<FontAwesomeIcon icon={faPencilSquare} size="lg" /> <FontAwesomeIcon icon={faEdit} />
</IconButton> </IconButton>
)} )}
</ProjectPermissionCan> </ProjectPermissionCan>
@@ -228,6 +223,7 @@ export const DynamicSecretListView = ({
<IconButton <IconButton
ariaLabel="delete-dynamic-secret" ariaLabel="delete-dynamic-secret"
variant="plain" variant="plain"
colorSchema="danger"
size="md" size="md"
className="p-0 opacity-0 group-hover:opacity-100" className="p-0 opacity-0 group-hover:opacity-100"
onClick={(evt) => { onClick={(evt) => {
@@ -236,7 +232,7 @@ export const DynamicSecretListView = ({
}} }}
isDisabled={!isAllowed || isRevoking} isDisabled={!isAllowed || isRevoking}
> >
<FontAwesomeIcon icon={faClose} size="lg" /> <FontAwesomeIcon icon={faTrash} />
</IconButton> </IconButton>
)} )}
</ProjectPermissionCan> </ProjectPermissionCan>
@@ -1,5 +1,12 @@
import { subject } from "@casl/ability"; import { subject } from "@casl/ability";
import { faClose, faFolder, faInfoCircle, faPencilSquare } from "@fortawesome/free-solid-svg-icons"; import {
faClose,
faEdit,
faFolder,
faInfoCircle,
faPencilSquare,
faTrash
} from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { useNavigate, useSearch } from "@tanstack/react-router"; import { useNavigate, useSearch } from "@tanstack/react-router";
import { twMerge } from "tailwind-merge"; import { twMerge } from "tailwind-merge";
@@ -251,7 +258,7 @@ export const FolderListView = ({
</IconButton> </IconButton>
</div> </div>
) : ( ) : (
<div className="flex items-center space-x-4 border-l border-mineshaft-600 px-3 py-3"> <div className="flex w-16 items-center justify-between border-l border-mineshaft-600 px-3 py-3">
<ProjectPermissionCan <ProjectPermissionCan
I={ProjectPermissionActions.Edit} I={ProjectPermissionActions.Edit}
a={subject(ProjectPermissionSub.SecretFolders, { environment, secretPath })} a={subject(ProjectPermissionSub.SecretFolders, { environment, secretPath })}
@@ -267,7 +274,7 @@ export const FolderListView = ({
onClick={() => handlePopUpOpen("updateFolder", { id, name, description })} onClick={() => handlePopUpOpen("updateFolder", { id, name, description })}
isDisabled={!isAllowed} isDisabled={!isAllowed}
> >
<FontAwesomeIcon icon={faPencilSquare} size="lg" /> <FontAwesomeIcon icon={faEdit} />
</IconButton> </IconButton>
)} )}
</ProjectPermissionCan> </ProjectPermissionCan>
@@ -281,12 +288,13 @@ export const FolderListView = ({
<IconButton <IconButton
ariaLabel="delete-folder" ariaLabel="delete-folder"
variant="plain" variant="plain"
colorSchema="danger"
size="md" size="md"
className="p-0 opacity-0 group-hover:opacity-100" className="p-0 opacity-0 group-hover:opacity-100"
onClick={() => handlePopUpOpen("deleteFolder", { id, name })} onClick={() => handlePopUpOpen("deleteFolder", { id, name })}
isDisabled={!isAllowed} isDisabled={!isAllowed}
> >
<FontAwesomeIcon icon={faClose} size="lg" /> <FontAwesomeIcon icon={faTrash} />
</IconButton> </IconButton>
)} )}
</ProjectPermissionCan> </ProjectPermissionCan>
@@ -3,13 +3,13 @@ import { subject } from "@casl/ability";
import { useSortable } from "@dnd-kit/sortable"; import { useSortable } from "@dnd-kit/sortable";
import { import {
faCalendarCheck, faCalendarCheck,
faClose,
faFileImport, faFileImport,
faFolder, faFolder,
faInfoCircle, faInfoCircle,
faKey, faKey,
faRotate, faRotate,
faSearch, faSearch,
faTrash,
faUpDown, faUpDown,
faWarning, faWarning,
faXmark faXmark
@@ -237,7 +237,7 @@ export const SecretImportItem = ({
</ProjectPermissionCan> </ProjectPermissionCan>
)} )}
</div> </div>
<div className="flex items-center space-x-4 border-l border-mineshaft-600 px-4 py-2"> <div className="flex w-16 items-center justify-between border-l border-mineshaft-600 py-3.5 pl-4 pr-3">
<ProjectPermissionCan <ProjectPermissionCan
I={ProjectPermissionActions.Edit} I={ProjectPermissionActions.Edit}
a={subject(ProjectPermissionSub.SecretImports, { a={subject(ProjectPermissionSub.SecretImports, {
@@ -281,7 +281,7 @@ export const SecretImportItem = ({
}} }}
isDisabled={!isAllowed} isDisabled={!isAllowed}
> >
<FontAwesomeIcon icon={faClose} size="lg" /> <FontAwesomeIcon icon={faTrash} />
</IconButton> </IconButton>
)} )}
</ProjectPermissionCan> </ProjectPermissionCan>
@@ -788,18 +788,18 @@ export const SecretItem = memo(
{isInAutoSaveMode ? ( {isInAutoSaveMode ? (
<motion.div <motion.div
key="auto-save-mode" key="auto-save-mode"
className="flex h-10 flex-shrink-0 items-center space-x-4 px-[0.64rem]" className="flex w-[63px] flex-shrink-0 items-center justify-between px-3"
initial={{ x: -10, opacity: 0 }} initial={{ x: -10, opacity: 0 }}
animate={{ x: 0, opacity: 1 }} animate={{ x: 0, opacity: 1 }}
exit={{ x: -10, opacity: 0 }} exit={{ x: -10, opacity: 0 }}
> >
<div className="h-12 w-12" /> <div className="h-10 w-12" />
</motion.div> </motion.div>
) : !isDirty ? ( ) : !isDirty ? (
isPending ? ( isPending ? (
<motion.div <motion.div
key="options" key="options"
className="mt-0.5 flex h-10 flex-shrink-0 items-center space-x-3 px-[0.84rem]" className="flex w-[63px] flex-shrink-0 items-center justify-between px-3"
initial={{ x: 0, opacity: 0 }} initial={{ x: 0, opacity: 0 }}
animate={{ x: 0, opacity: 1 }} animate={{ x: 0, opacity: 1 }}
exit={{ x: 10, opacity: 0 }} exit={{ x: 10, opacity: 0 }}
@@ -839,7 +839,7 @@ export const SecretItem = memo(
) : ( ) : (
<motion.div <motion.div
key="options" key="options"
className="mt-0.5 flex h-10 flex-shrink-0 items-center space-x-3 px-[0.84rem]" className="flex w-[63px] flex-shrink-0 items-center justify-between px-3"
initial={{ x: 0, opacity: 0 }} initial={{ x: 0, opacity: 0 }}
animate={{ x: 0, opacity: 1 }} animate={{ x: 0, opacity: 1 }}
exit={{ x: 10, opacity: 0 }} exit={{ x: 10, opacity: 0 }}
@@ -881,7 +881,7 @@ export const SecretItem = memo(
> >
<FontAwesomeSymbol <FontAwesomeSymbol
symbolName={FontAwesomeSpriteName.Trash} symbolName={FontAwesomeSpriteName.Trash}
className="h-3.5 w-4" className="h-4 w-3"
/> />
</IconButton> </IconButton>
)} )}
@@ -891,7 +891,7 @@ export const SecretItem = memo(
) : ( ) : (
<motion.div <motion.div
key="options-save" key="options-save"
className="flex h-10 flex-shrink-0 items-center space-x-4 px-3" className="flex w-[63px] flex-shrink-0 items-center justify-between px-3"
initial={{ x: -10, opacity: 0 }} initial={{ x: -10, opacity: 0 }}
animate={{ x: 0, opacity: 1 }} animate={{ x: 0, opacity: 1 }}
exit={{ x: -10, opacity: 0 }} exit={{ x: -10, opacity: 0 }}
@@ -2,11 +2,11 @@ import { useState } from "react";
import { subject } from "@casl/ability"; import { subject } from "@casl/ability";
import { import {
faAsterisk, faAsterisk,
faClose,
faEdit, faEdit,
faInfoCircle, faInfoCircle,
faKey, faKey,
faRotate faRotate,
faTrash
} from "@fortawesome/free-solid-svg-icons"; } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { AnimatePresence, motion } from "framer-motion"; import { AnimatePresence, motion } from "framer-motion";
@@ -48,7 +48,7 @@ export const SecretRotationItem = ({
<div className="text- flex w-11 items-center py-2 pl-5 text-mineshaft-400"> <div className="text- flex w-11 items-center py-2 pl-5 text-mineshaft-400">
<FontAwesomeIcon icon={faRotate} /> <FontAwesomeIcon icon={faRotate} />
</div> </div>
<div className="flex flex-grow items-center border-r border-mineshaft-600 py-2 pl-4 pr-2"> <div className="flex flex-grow items-center py-2 pl-4 pr-2">
<div className="flex w-full flex-wrap items-center"> <div className="flex w-full flex-wrap items-center">
<span>{name}</span> <span>{name}</span>
<Tag className="mx-2.5 flex items-center gap-1 px-1.5 py-0 text-xs normal-case"> <Tag className="mx-2.5 flex items-center gap-1 px-1.5 py-0 text-xs normal-case">
@@ -141,7 +141,7 @@ export const SecretRotationItem = ({
<AnimatePresence mode="wait"> <AnimatePresence mode="wait">
<motion.div <motion.div
key="options" key="options"
className="flex h-10 flex-shrink-0 items-center space-x-1 px-[0.665rem]" className="flex w-16 items-center justify-between border-l border-mineshaft-600 px-2 py-3"
initial={{ x: 0, opacity: 0 }} initial={{ x: 0, opacity: 0 }}
animate={{ x: 0, opacity: 1 }} animate={{ x: 0, opacity: 1 }}
exit={{ x: 10, opacity: 0 }} exit={{ x: 10, opacity: 0 }}
@@ -159,7 +159,6 @@ export const SecretRotationItem = ({
<IconButton <IconButton
ariaLabel="Edit rotation" ariaLabel="Edit rotation"
variant="plain" variant="plain"
size="md"
isDisabled={!isAllowed} isDisabled={!isAllowed}
className="opacity-0 group-hover:opacity-100" className="opacity-0 group-hover:opacity-100"
onClick={(e) => { onClick={(e) => {
@@ -185,7 +184,6 @@ export const SecretRotationItem = ({
ariaLabel="Delete rotation" ariaLabel="Delete rotation"
variant="plain" variant="plain"
colorSchema="danger" colorSchema="danger"
size="md"
className="opacity-0 group-hover:opacity-100" className="opacity-0 group-hover:opacity-100"
onClick={(e) => { onClick={(e) => {
e.stopPropagation(); e.stopPropagation();
@@ -193,7 +191,7 @@ export const SecretRotationItem = ({
}} }}
isDisabled={!isAllowed} isDisabled={!isAllowed}
> >
<FontAwesomeIcon icon={faClose} size="lg" /> <FontAwesomeIcon icon={faTrash} />
</IconButton> </IconButton>
)} )}
</ProjectPermissionCan> </ProjectPermissionCan>