mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-09-22 13:39:35 +00:00
improvements: revise ui elements
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -41,7 +41,8 @@ export const CopyButton = ({
|
||||
variant={variant}
|
||||
className={twMerge("group relative", color)}
|
||||
size={size}
|
||||
onClick={() => {
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
handleCopyText();
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 = (
|
||||
<div>
|
||||
{indent}
|
||||
<span>{"}"}</span>
|
||||
{comma}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -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<HTMLDivElement>(null);
|
||||
const newContainerRef = useRef<HTMLDivElement>(null);
|
||||
const [internalCollapsed, setInternalCollapsed] = useState(isCollapsed);
|
||||
@@ -610,6 +614,20 @@ export const SecretVersionDiffView = ({
|
||||
<p className={twMerge(textStyle, "truncate")}>{key}</p>
|
||||
{changeBadge}
|
||||
</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
|
||||
icon={collapsed ? faChevronDown : faChevronUp}
|
||||
className="ml-2 text-gray-400"
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
/* 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 { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
|
||||
import { Badge, Button, Input, Modal, ModalContent } from "@app/components/v2";
|
||||
import { useToggle } from "@app/hooks";
|
||||
import { PendingAction } from "@app/hooks/api/secretFolders/types";
|
||||
import { SecretVersionDiffView } from "@app/pages/secret-manager/CommitDetailsPage/components/SecretVersionDiffView";
|
||||
|
||||
@@ -23,356 +22,242 @@ interface CommitFormProps {
|
||||
secretPath: string;
|
||||
}
|
||||
|
||||
interface ChangeTableProps {
|
||||
interface ResourceChangeProps {
|
||||
change: PendingChange;
|
||||
environment: string;
|
||||
workspaceId: string;
|
||||
secretPath: string;
|
||||
}
|
||||
|
||||
const TagsList: React.FC<{ tags?: { id: string; slug: string }[]; className?: string }> = ({
|
||||
tags,
|
||||
className = ""
|
||||
}) => {
|
||||
if (!tags || tags.length === 0) {
|
||||
return <span className={`italic text-mineshaft-400 ${className}`}>(no tags)</span>;
|
||||
type RenderResourceProps = {
|
||||
onDiscard: () => void;
|
||||
change: PendingChange;
|
||||
};
|
||||
|
||||
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 (
|
||||
<div className={`flex flex-wrap gap-1 ${className}`}>
|
||||
{tags.map((tag) => (
|
||||
<Badge key={tag.id} variant="success" className="text-xs">
|
||||
{tag.slug}
|
||||
</Badge>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
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 <span className={`italic text-mineshaft-400 ${className}`}>(no metadata)</span>;
|
||||
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
|
||||
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 (
|
||||
<div className={`space-y-1 ${className}`}>
|
||||
{metadata.map((item) => (
|
||||
<div key={item.key} className="flex items-center gap-2 text-xs">
|
||||
<span className="font-medium text-mineshaft-300">{item.key}:</span>
|
||||
<span className="font-mono text-mineshaft-100">{item.value}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
if (change.type === PendingAction.Delete) {
|
||||
const { secretKey, secretValue } = change;
|
||||
return (
|
||||
<SecretVersionDiffView
|
||||
onDiscard={onDiscard}
|
||||
item={{
|
||||
secretKey: change.secretKey,
|
||||
isDeleted: true,
|
||||
type: "secret",
|
||||
id: change.id,
|
||||
versions: [
|
||||
{
|
||||
version: 1, // placeholder, not used
|
||||
secretKey,
|
||||
secretValue
|
||||
}
|
||||
]
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
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 (
|
||||
<SecretVersionDiffView
|
||||
onDiscard={onDiscard}
|
||||
item={{
|
||||
folderName: change.folderName,
|
||||
isAdded: true,
|
||||
type: "folder",
|
||||
id: change.id,
|
||||
versions: [
|
||||
{
|
||||
version: 1,
|
||||
name: change.folderName,
|
||||
description: change.description
|
||||
}
|
||||
]
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<tr className="border-b border-mineshaft-700 last:border-b-0">
|
||||
<td className="w-[12%] border-r border-mineshaft-600 px-4 py-3 align-top font-medium text-mineshaft-300">
|
||||
{label}
|
||||
</td>
|
||||
<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>
|
||||
</td>
|
||||
<td className="w-1/2 py-3 pr-4 align-top">
|
||||
<div className="px-4 text-green-400">{newValue}</div>
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
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 (
|
||||
<SecretVersionDiffView
|
||||
onDiscard={onDiscard}
|
||||
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,
|
||||
environment,
|
||||
workspaceId,
|
||||
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 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 (
|
||||
// <div className="py-2 shadow-sm">
|
||||
// <div className="flex items-center justify-between">
|
||||
// <div className="flex items-center gap-3">
|
||||
// <span className="font-medium text-mineshaft-100">{getChangeName()}</span>
|
||||
// {getChangeBadge(change.type)}
|
||||
// </div>
|
||||
// <Tooltip content="Discard change">
|
||||
// <IconButton
|
||||
// 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>
|
||||
// );
|
||||
return change.resourceType === "secret" ? (
|
||||
<RenderSecretChanges
|
||||
key={change.id}
|
||||
change={change}
|
||||
onDiscard={() => handleDeletePending(change.resourceType, change.id)}
|
||||
/>
|
||||
) : (
|
||||
<RenderFolderChanges
|
||||
key={change.id}
|
||||
change={change}
|
||||
onDiscard={() => handleDeletePending(change.resourceType, change.id)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export const CommitForm: React.FC<CommitFormProps> = ({
|
||||
@@ -460,12 +345,12 @@ export const CommitForm: React.FC<CommitFormProps> = ({
|
||||
</div>
|
||||
}
|
||||
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"
|
||||
>
|
||||
<div className="space-y-6">
|
||||
{/* Changes List */}
|
||||
<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 */}
|
||||
{pendingChanges.folders.length > 0 && (
|
||||
<div>
|
||||
@@ -475,7 +360,7 @@ export const CommitForm: React.FC<CommitFormProps> = ({
|
||||
</h4>
|
||||
<div>
|
||||
{pendingChanges.folders.map((change) => (
|
||||
<ChangeTable
|
||||
<ResourceChange
|
||||
key={change.id}
|
||||
change={change}
|
||||
environment={environment}
|
||||
@@ -490,13 +375,13 @@ export const CommitForm: React.FC<CommitFormProps> = ({
|
||||
{/* Secret Changes */}
|
||||
{pendingChanges.secrets.length > 0 && (
|
||||
<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" />
|
||||
Secrets ({pendingChanges.secrets.length})
|
||||
</h4>
|
||||
<div>
|
||||
{pendingChanges.secrets.map((change) => (
|
||||
<ChangeTable
|
||||
<ResourceChange
|
||||
key={change.id}
|
||||
change={change}
|
||||
environment={environment}
|
||||
|
||||
@@ -1,10 +1,5 @@
|
||||
import { subject } from "@casl/ability";
|
||||
import {
|
||||
faClose,
|
||||
faFingerprint,
|
||||
faPencilSquare,
|
||||
faWarning
|
||||
} from "@fortawesome/free-solid-svg-icons";
|
||||
import { faEdit, faFingerprint, faTrash, faWarning } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
|
||||
import { createNotification } from "@app/components/notifications";
|
||||
@@ -187,7 +182,7 @@ export const DynamicSecretListView = ({
|
||||
</Tooltip>
|
||||
)}
|
||||
</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
|
||||
I={ProjectPermissionDynamicSecretActions.EditRootCredential}
|
||||
a={subject(ProjectPermissionSub.DynamicSecrets, {
|
||||
@@ -210,7 +205,7 @@ export const DynamicSecretListView = ({
|
||||
}}
|
||||
isDisabled={!isAllowed || isRevoking}
|
||||
>
|
||||
<FontAwesomeIcon icon={faPencilSquare} size="lg" />
|
||||
<FontAwesomeIcon icon={faEdit} />
|
||||
</IconButton>
|
||||
)}
|
||||
</ProjectPermissionCan>
|
||||
@@ -228,6 +223,7 @@ export const DynamicSecretListView = ({
|
||||
<IconButton
|
||||
ariaLabel="delete-dynamic-secret"
|
||||
variant="plain"
|
||||
colorSchema="danger"
|
||||
size="md"
|
||||
className="p-0 opacity-0 group-hover:opacity-100"
|
||||
onClick={(evt) => {
|
||||
@@ -236,7 +232,7 @@ export const DynamicSecretListView = ({
|
||||
}}
|
||||
isDisabled={!isAllowed || isRevoking}
|
||||
>
|
||||
<FontAwesomeIcon icon={faClose} size="lg" />
|
||||
<FontAwesomeIcon icon={faTrash} />
|
||||
</IconButton>
|
||||
)}
|
||||
</ProjectPermissionCan>
|
||||
|
||||
@@ -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 = ({
|
||||
</IconButton>
|
||||
</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
|
||||
I={ProjectPermissionActions.Edit}
|
||||
a={subject(ProjectPermissionSub.SecretFolders, { environment, secretPath })}
|
||||
@@ -267,7 +274,7 @@ export const FolderListView = ({
|
||||
onClick={() => handlePopUpOpen("updateFolder", { id, name, description })}
|
||||
isDisabled={!isAllowed}
|
||||
>
|
||||
<FontAwesomeIcon icon={faPencilSquare} size="lg" />
|
||||
<FontAwesomeIcon icon={faEdit} />
|
||||
</IconButton>
|
||||
)}
|
||||
</ProjectPermissionCan>
|
||||
@@ -281,12 +288,13 @@ export const FolderListView = ({
|
||||
<IconButton
|
||||
ariaLabel="delete-folder"
|
||||
variant="plain"
|
||||
colorSchema="danger"
|
||||
size="md"
|
||||
className="p-0 opacity-0 group-hover:opacity-100"
|
||||
onClick={() => handlePopUpOpen("deleteFolder", { id, name })}
|
||||
isDisabled={!isAllowed}
|
||||
>
|
||||
<FontAwesomeIcon icon={faClose} size="lg" />
|
||||
<FontAwesomeIcon icon={faTrash} />
|
||||
</IconButton>
|
||||
)}
|
||||
</ProjectPermissionCan>
|
||||
|
||||
@@ -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 = ({
|
||||
</ProjectPermissionCan>
|
||||
)}
|
||||
</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
|
||||
I={ProjectPermissionActions.Edit}
|
||||
a={subject(ProjectPermissionSub.SecretImports, {
|
||||
@@ -281,7 +281,7 @@ export const SecretImportItem = ({
|
||||
}}
|
||||
isDisabled={!isAllowed}
|
||||
>
|
||||
<FontAwesomeIcon icon={faClose} size="lg" />
|
||||
<FontAwesomeIcon icon={faTrash} />
|
||||
</IconButton>
|
||||
)}
|
||||
</ProjectPermissionCan>
|
||||
|
||||
@@ -788,18 +788,18 @@ export const SecretItem = memo(
|
||||
{isInAutoSaveMode ? (
|
||||
<motion.div
|
||||
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 }}
|
||||
animate={{ x: 0, opacity: 1 }}
|
||||
exit={{ x: -10, opacity: 0 }}
|
||||
>
|
||||
<div className="h-12 w-12" />
|
||||
<div className="h-10 w-12" />
|
||||
</motion.div>
|
||||
) : !isDirty ? (
|
||||
isPending ? (
|
||||
<motion.div
|
||||
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 }}
|
||||
animate={{ x: 0, opacity: 1 }}
|
||||
exit={{ x: 10, opacity: 0 }}
|
||||
@@ -839,7 +839,7 @@ export const SecretItem = memo(
|
||||
) : (
|
||||
<motion.div
|
||||
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 }}
|
||||
animate={{ x: 0, opacity: 1 }}
|
||||
exit={{ x: 10, opacity: 0 }}
|
||||
@@ -881,7 +881,7 @@ export const SecretItem = memo(
|
||||
>
|
||||
<FontAwesomeSymbol
|
||||
symbolName={FontAwesomeSpriteName.Trash}
|
||||
className="h-3.5 w-4"
|
||||
className="h-4 w-3"
|
||||
/>
|
||||
</IconButton>
|
||||
)}
|
||||
@@ -891,7 +891,7 @@ export const SecretItem = memo(
|
||||
) : (
|
||||
<motion.div
|
||||
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 }}
|
||||
animate={{ x: 0, opacity: 1 }}
|
||||
exit={{ x: -10, opacity: 0 }}
|
||||
|
||||
@@ -2,11 +2,11 @@ import { useState } from "react";
|
||||
import { subject } from "@casl/ability";
|
||||
import {
|
||||
faAsterisk,
|
||||
faClose,
|
||||
faEdit,
|
||||
faInfoCircle,
|
||||
faKey,
|
||||
faRotate
|
||||
faRotate,
|
||||
faTrash
|
||||
} from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
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">
|
||||
<FontAwesomeIcon icon={faRotate} />
|
||||
</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">
|
||||
<span>{name}</span>
|
||||
<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">
|
||||
<motion.div
|
||||
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 }}
|
||||
animate={{ x: 0, opacity: 1 }}
|
||||
exit={{ x: 10, opacity: 0 }}
|
||||
@@ -159,7 +159,6 @@ export const SecretRotationItem = ({
|
||||
<IconButton
|
||||
ariaLabel="Edit rotation"
|
||||
variant="plain"
|
||||
size="md"
|
||||
isDisabled={!isAllowed}
|
||||
className="opacity-0 group-hover:opacity-100"
|
||||
onClick={(e) => {
|
||||
@@ -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}
|
||||
>
|
||||
<FontAwesomeIcon icon={faClose} size="lg" />
|
||||
<FontAwesomeIcon icon={faTrash} />
|
||||
</IconButton>
|
||||
)}
|
||||
</ProjectPermissionCan>
|
||||
|
||||
Reference in New Issue
Block a user