From f29593eb60b42f36a0e72c8bd5481b64f623a95c Mon Sep 17 00:00:00 2001 From: Scott Wilson Date: Mon, 21 Jul 2025 12:09:44 -0700 Subject: [PATCH] improvements: revise ui elements --- .../src/ee/services/license/license-fns.ts | 4 +- .../components/v2/CopyButton/CopyButton.tsx | 3 +- frontend/src/hooks/useNavigationBlocker.tsx | 6 +- .../SecretVersionDiffView.tsx | 28 +- .../components/CommitForm/CommitForm.tsx | 549 +++++++----------- .../DynamicSecretListView.tsx | 14 +- .../FolderListView/FolderListView.tsx | 16 +- .../SecretImportListView/SecretImportItem.tsx | 6 +- .../components/SecretListView/SecretItem.tsx | 12 +- .../SecretRotationItem.tsx | 12 +- 10 files changed, 280 insertions(+), 370 deletions(-) diff --git a/backend/src/ee/services/license/license-fns.ts b/backend/src/ee/services/license/license-fns.ts index 3df2ad942..5b755567b 100644 --- a/backend/src/ee/services/license/license-fns.ts +++ b/backend/src/ee/services/license/license-fns.ts @@ -20,7 +20,7 @@ export const getDefaultOnPremFeatures = (): TFeatureSet => ({ identitiesUsed: 0, dynamicSecret: false, secretVersioning: true, - pitRecovery: true, + pitRecovery: false, ipAllowlisting: false, rbac: false, githubOrgSync: false, @@ -40,7 +40,7 @@ export const getDefaultOnPremFeatures = (): TFeatureSet => ({ status: null, trial_end: null, has_used_trial: true, - secretApproval: true, + secretApproval: false, secretRotation: false, caCrl: false, instanceUserManagement: false, diff --git a/frontend/src/components/v2/CopyButton/CopyButton.tsx b/frontend/src/components/v2/CopyButton/CopyButton.tsx index ff1161ca9..135dec366 100644 --- a/frontend/src/components/v2/CopyButton/CopyButton.tsx +++ b/frontend/src/components/v2/CopyButton/CopyButton.tsx @@ -41,7 +41,8 @@ export const CopyButton = ({ variant={variant} className={twMerge("group relative", color)} size={size} - onClick={() => { + onClick={(e) => { + e.stopPropagation(); handleCopyText(); }} > diff --git a/frontend/src/hooks/useNavigationBlocker.tsx b/frontend/src/hooks/useNavigationBlocker.tsx index da461066b..e427d6a7d 100644 --- a/frontend/src/hooks/useNavigationBlocker.tsx +++ b/frontend/src/hooks/useNavigationBlocker.tsx @@ -1,6 +1,10 @@ import { useCallback } from "react"; 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 = { isBlocked: boolean; diff --git a/frontend/src/pages/secret-manager/CommitDetailsPage/components/SecretVersionDiffView/SecretVersionDiffView.tsx b/frontend/src/pages/secret-manager/CommitDetailsPage/components/SecretVersionDiffView/SecretVersionDiffView.tsx index b132a3b40..789a17f0f 100644 --- a/frontend/src/pages/secret-manager/CommitDetailsPage/components/SecretVersionDiffView/SecretVersionDiffView.tsx +++ b/frontend/src/pages/secret-manager/CommitDetailsPage/components/SecretVersionDiffView/SecretVersionDiffView.tsx @@ -1,9 +1,11 @@ /* eslint-disable no-nested-ternary */ 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 { twMerge } from "tailwind-merge"; +import { IconButton, Tooltip } from "@app/components/v2"; + export interface Version { id?: string; version: number; @@ -33,6 +35,7 @@ interface SecretVersionDiffViewProps { showHeader?: boolean; customHeader?: JSX.Element; excludedFieldsHighlight?: string[]; + onDiscard?: VoidFunction; } const isObject = (obj: JsonValue): obj is JsonObject => { @@ -409,7 +412,6 @@ const renderJsonWithDiffs = (
{indent} {"}"} - {comma}
@@ -465,7 +467,9 @@ const formatDeletedJson = (json: JsonValue): JSX.Element => { const cleanVersionForComparison = (version: Version): JsonValue => { 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 = ({ @@ -474,9 +478,9 @@ export const SecretVersionDiffView = ({ onToggleCollapse, showHeader = true, customHeader, - excludedFieldsHighlight = ["metadata", "tags"] + excludedFieldsHighlight = ["metadata", "tags"], + onDiscard }: SecretVersionDiffViewProps) => { - console.log("item", item); const oldContainerRef = useRef(null); const newContainerRef = useRef(null); const [internalCollapsed, setInternalCollapsed] = useState(isCollapsed); @@ -610,6 +614,20 @@ export const SecretVersionDiffView = ({

{key}

{changeBadge} + {onDiscard && ( + + + + + + )} = ({ - tags, - className = "" -}) => { - if (!tags || tags.length === 0) { - return (no tags); +type RenderResourceProps = { + onDiscard: () => void; + change: PendingChange; +}; + +const RenderSecretChanges = ({ onDiscard, change }: RenderResourceProps) => { + if (change.resourceType !== "secret") return null; + + if (change.type === PendingAction.Create) { + return ( + + ); } - return ( -
- {tags.map((tag) => ( - - {tag.slug} - - ))} -
- ); -}; + if (change.type === PendingAction.Update) { + const { existingSecret } = change; -const MetadataList: React.FC<{ - metadata?: { key: string; value: string }[]; - className?: string; -}> = ({ metadata, className = "" }) => { - if (!metadata || metadata.length === 0) { - return (no metadata); + 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 ( + 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 ( -
- {metadata.map((item) => ( -
- {item.key}: - {item.value} -
- ))} -
- ); + if (change.type === PendingAction.Delete) { + const { secretKey, secretValue } = change; + return ( + + ); + } + + return null; }; -const ComparisonTableRow: React.FC<{ - label: string; - previousValue: React.ReactNode; - newValue: React.ReactNode; - hideIfSame?: boolean; -}> = ({ label, previousValue, newValue, hideIfSame = false }) => { - const isSame = hideIfSame && String(previousValue) === String(newValue); +const RenderFolderChanges = ({ onDiscard, change }: RenderResourceProps) => { + if (change.resourceType !== "folder") return null; - if (isSame) return null; + if (change.type === PendingAction.Create) { + return ( + + ); + } - return ( - - - {label} - - -
{previousValue}
- - -
{newValue}
- - - ); + 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 ( + + ); + } + + if (change.type === PendingAction.Delete) { + return ( + + ); + } + + return null; }; -const ChangeTable: React.FC = ({ +const ResourceChange: React.FC = ({ change, environment, workspaceId, secretPath }) => { - const [isOpen, setIsOpen] = useToggle(true); - - const getChangeBadge = (type: PendingChange["type"]) => { - switch (type) { - case PendingAction.Create: - return Created; - case PendingAction.Update: - return Updated; - case PendingAction.Delete: - return Deleted; - default: - return null; - } - }; - - const renderSecretChanges = () => { - if (change.resourceType !== "secret") return null; - - if (change.type === PendingAction.Create) { - return ( -
- - - - - - - - - - - {change.secretComment !== undefined && change.secretComment !== "" && ( - - - - - )} - {change.tags && change.tags.length > 0 && ( - - - - - )} - {change.secretMetadata && change.secretMetadata.length > 0 && ( - - - - - )} - -
Key: - {change.secretKey} -
Value: -
- {change.secretValue || ( - (empty) - )} -
-
Comment: - {change.secretComment} -
Tags: - -
Metadata: - -
-
- ); - } - - 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 ( - - ); - } - - if (change.type === PendingAction.Delete) { - const { secretKey, secretValue } = change; - return ( - - ); - } - - return null; - }; - - const renderFolderChanges = () => { - if (change.resourceType !== "folder") return null; - - if (change.type === PendingAction.Create) { - return ( - - - - - - - {change.description !== undefined && change.description !== "" && ( - - - - - )} - -
Name: - {change.folderName} -
Description: - {change.description} -
- ); - } - - 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 ( - - - {hasNameChange && ( - {change.originalFolderName}} - newValue={{change.folderName}} - /> - )} - {hasDescriptionChange && ( - (empty) - } - newValue={change.description || (empty)} - /> - )} - -
- ); - } - - if (change.type === PendingAction.Delete) { - return ( - - - - - - - -
Name: - {change.folderName} -
- ); - } - - 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 handleDeletePending = (changeType: string, id: string) => { - removePendingChange(id, changeType, { - workspaceId, - environment, - secretPath - }); - }; + const handleDeletePending = useCallback( + (changeType: string, id: string) => { + removePendingChange(id, changeType, { + workspaceId, + environment, + secretPath + }); + }, + [change.resourceType, change.id] + ); - return change.resourceType === "secret" ? renderSecretChanges() : renderFolderChanges(); - - // return ( - //
- //
- //
- // {getChangeName()} - // {getChangeBadge(change.type)} - //
- // - // handleDeletePending(change.resourceType, change.id)} - // > - // - // - // - //
- // {change.resourceType === "secret" ? renderSecretChanges() : renderFolderChanges()} - //
- // ); + return change.resourceType === "secret" ? ( + handleDeletePending(change.resourceType, change.id)} + /> + ) : ( + handleDeletePending(change.resourceType, change.id)} + /> + ); }; export const CommitForm: React.FC = ({ @@ -460,12 +345,12 @@ export const CommitForm: React.FC = ({ } subTitle={"Write a commit message and review the changes you're about to commit."} - className="max-h-[90vh] max-w-5xl" + className="max-h-[90vh] max-w-[95%] md:max-w-7xl" >
{/* Changes List */}
-
+
{/* Folder Changes */} {pendingChanges.folders.length > 0 && (
@@ -475,7 +360,7 @@ export const CommitForm: React.FC = ({
{pendingChanges.folders.map((change) => ( - = ({ {/* Secret Changes */} {pendingChanges.secrets.length > 0 && (
-

+

Secrets ({pendingChanges.secrets.length})

{pendingChanges.secrets.map((change) => ( - )}
-
+
- + )} @@ -228,6 +223,7 @@ export const DynamicSecretListView = ({ { @@ -236,7 +232,7 @@ export const DynamicSecretListView = ({ }} isDisabled={!isAllowed || isRevoking} > - + )} diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/FolderListView/FolderListView.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/FolderListView/FolderListView.tsx index 8859a95fc..e0cc3f78b 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/FolderListView/FolderListView.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/FolderListView/FolderListView.tsx @@ -1,5 +1,12 @@ 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 { useNavigate, useSearch } from "@tanstack/react-router"; import { twMerge } from "tailwind-merge"; @@ -251,7 +258,7 @@ export const FolderListView = ({
) : ( -
+
handlePopUpOpen("updateFolder", { id, name, description })} isDisabled={!isAllowed} > - + )} @@ -281,12 +288,13 @@ export const FolderListView = ({ handlePopUpOpen("deleteFolder", { id, name })} isDisabled={!isAllowed} > - + )} diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/SecretImportListView/SecretImportItem.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/SecretImportListView/SecretImportItem.tsx index 2be935b33..44886c21f 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/SecretImportListView/SecretImportItem.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/SecretImportListView/SecretImportItem.tsx @@ -3,13 +3,13 @@ import { subject } from "@casl/ability"; import { useSortable } from "@dnd-kit/sortable"; import { faCalendarCheck, - faClose, faFileImport, faFolder, faInfoCircle, faKey, faRotate, faSearch, + faTrash, faUpDown, faWarning, faXmark @@ -237,7 +237,7 @@ export const SecretImportItem = ({ )}
-
+
- + )} diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/SecretListView/SecretItem.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/SecretListView/SecretItem.tsx index f55479593..707284fcf 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/SecretListView/SecretItem.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/SecretListView/SecretItem.tsx @@ -788,18 +788,18 @@ export const SecretItem = memo( {isInAutoSaveMode ? ( -
+
) : !isDirty ? ( isPending ? ( )} @@ -891,7 +891,7 @@ export const SecretItem = memo( ) : (
-
+
{name} @@ -141,7 +141,7 @@ export const SecretRotationItem = ({ { @@ -185,7 +184,6 @@ export const SecretRotationItem = ({ ariaLabel="Delete rotation" variant="plain" colorSchema="danger" - size="md" className="opacity-0 group-hover:opacity-100" onClick={(e) => { e.stopPropagation(); @@ -193,7 +191,7 @@ export const SecretRotationItem = ({ }} isDisabled={!isAllowed} > - + )}