mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-03 03:25:48 +00:00
improvements: revise ui elements
This commit is contained in:
@@ -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();
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
+23
-5
@@ -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"
|
||||||
|
|||||||
+217
-332
@@ -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're about to commit."}
|
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">
|
<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}
|
||||||
|
|||||||
+5
-9
@@ -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>
|
||||||
|
|||||||
+12
-4
@@ -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
-3
@@ -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>
|
||||||
|
|||||||
+6
-6
@@ -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 }}
|
||||||
|
|||||||
+5
-7
@@ -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>
|
||||||
|
|||||||
Reference in New Issue
Block a user