mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-07 12:27:28 +00:00
improvements: address feedback and additional bugs
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
import { DetailedHTMLProps, HTMLAttributes, ReactNode, TdHTMLAttributes } from "react";
|
import { DetailedHTMLProps, forwardRef, HTMLAttributes, ReactNode, TdHTMLAttributes } from "react";
|
||||||
import { twMerge } from "tailwind-merge";
|
import { twMerge } from "tailwind-merge";
|
||||||
|
|
||||||
import { Skeleton } from "../Skeleton";
|
import { Skeleton } from "../Skeleton";
|
||||||
@@ -9,22 +9,20 @@ export type TableContainerProps = {
|
|||||||
className?: string;
|
className?: string;
|
||||||
} & DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>;
|
} & DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>;
|
||||||
|
|
||||||
export const TableContainer = ({
|
export const TableContainer = forwardRef<HTMLDivElement, TableContainerProps>(
|
||||||
children,
|
({ children, className, isRounded = true, ...props }, ref): JSX.Element => (
|
||||||
className,
|
<div
|
||||||
isRounded = true,
|
ref={ref}
|
||||||
...props
|
className={twMerge(
|
||||||
}: TableContainerProps): JSX.Element => (
|
"relative w-full overflow-x-auto border border-solid border-mineshaft-700 bg-mineshaft-800 font-inter",
|
||||||
<div
|
isRounded && "rounded-lg",
|
||||||
className={twMerge(
|
className
|
||||||
"relative w-full overflow-x-auto border border-solid border-mineshaft-700 bg-mineshaft-800 font-inter",
|
)}
|
||||||
isRounded && "rounded-lg",
|
{...props}
|
||||||
className
|
>
|
||||||
)}
|
{children}
|
||||||
{...props}
|
</div>
|
||||||
>
|
)
|
||||||
{children}
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
|
|
||||||
// main parent table
|
// main parent table
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { useCallback } from "react";
|
import { useCallback } from "react";
|
||||||
import { useQuery, UseQueryOptions } from "@tanstack/react-query";
|
import { useQuery, UseQueryOptions } from "@tanstack/react-query";
|
||||||
|
import { AxiosError } from "axios";
|
||||||
|
|
||||||
import { apiRequest } from "@app/config/request";
|
import { apiRequest } from "@app/config/request";
|
||||||
import {
|
import {
|
||||||
@@ -273,6 +274,12 @@ export const useGetProjectSecretsDetails = (
|
|||||||
...options,
|
...options,
|
||||||
// wait for all values to be available
|
// wait for all values to be available
|
||||||
enabled: Boolean(projectId) && (options?.enabled ?? true),
|
enabled: Boolean(projectId) && (options?.enabled ?? true),
|
||||||
|
retry: (count, error) => {
|
||||||
|
// don't retry 404s
|
||||||
|
if (error instanceof AxiosError && error.status === 404) return false;
|
||||||
|
|
||||||
|
return count <= 5;
|
||||||
|
},
|
||||||
queryKey: dashboardKeys.getProjectSecretsDetails({
|
queryKey: dashboardKeys.getProjectSecretsDetails({
|
||||||
secretPath,
|
secretPath,
|
||||||
search,
|
search,
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ export type DashboardProjectSecretsOverview = Omit<
|
|||||||
DashboardProjectSecretsOverviewResponse,
|
DashboardProjectSecretsOverviewResponse,
|
||||||
"secrets" | "secretRotations"
|
"secrets" | "secretRotations"
|
||||||
> & {
|
> & {
|
||||||
secrets?: SecretV3RawSanitized[];
|
secrets?: (SecretV3RawSanitized & { sourceEnv?: string })[];
|
||||||
secretRotations?: (TSecretRotationV2 & {
|
secretRotations?: (TSecretRotationV2 & {
|
||||||
secrets: (SecretV3RawSanitized | null)[];
|
secrets: (SecretV3RawSanitized | null)[];
|
||||||
})[];
|
})[];
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
import { MouseEvent, useCallback, useEffect, useRef, useState } from "react";
|
import { MouseEvent, RefObject, useCallback, useEffect, useRef, useState } from "react";
|
||||||
|
|
||||||
type Params = {
|
type Params = {
|
||||||
minWidth: number;
|
minWidth: number;
|
||||||
maxWidth: number;
|
maxWidth: number;
|
||||||
initialWidth: number;
|
initialWidth: number;
|
||||||
|
ref: RefObject<HTMLTableElement>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useResizableColWidth = ({ minWidth, maxWidth, initialWidth }: Params) => {
|
export const useResizableColWidth = ({ minWidth, maxWidth, initialWidth, ref }: Params) => {
|
||||||
const [colWidth, setColWidth] = useState(initialWidth);
|
const [colWidth, setColWidth] = useState(initialWidth);
|
||||||
const [isResizing, setIsResizing] = useState(false);
|
const [isResizing, setIsResizing] = useState(false);
|
||||||
const startX = useRef(0);
|
const startX = useRef(0);
|
||||||
@@ -63,6 +64,28 @@ export const useResizableColWidth = ({ minWidth, maxWidth, initialWidth }: Param
|
|||||||
};
|
};
|
||||||
}, [isResizing, handleMouseMove, handleMouseUp]);
|
}, [isResizing, handleMouseMove, handleMouseUp]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const element = ref?.current;
|
||||||
|
if (!element) return;
|
||||||
|
|
||||||
|
const handleResize = () => {
|
||||||
|
if (colWidth > maxWidth) {
|
||||||
|
setColWidth(Math.max(maxWidth, minWidth));
|
||||||
|
} else if (ref.current?.clientWidth && colWidth > ref.current.clientWidth * 0.9) {
|
||||||
|
// this else is a fallback to ensure col is always visible
|
||||||
|
setColWidth(initialWidth);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const resizeObserver = new ResizeObserver(handleResize);
|
||||||
|
resizeObserver.observe(element);
|
||||||
|
|
||||||
|
// eslint-disable-next-line consistent-return
|
||||||
|
return () => {
|
||||||
|
resizeObserver.disconnect();
|
||||||
|
};
|
||||||
|
}, [ref, maxWidth, colWidth]);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
colWidth,
|
colWidth,
|
||||||
handleMouseDown,
|
handleMouseDown,
|
||||||
|
|||||||
@@ -130,7 +130,11 @@ export const useSecretOverview = (secrets: DashboardProjectSecretsOverview["secr
|
|||||||
|
|
||||||
const getEnvSecretKeyCount = useCallback(
|
const getEnvSecretKeyCount = useCallback(
|
||||||
(env: string) => {
|
(env: string) => {
|
||||||
return secrets?.filter((secret) => secret.env === env).length ?? 0;
|
return (
|
||||||
|
secrets?.filter((secret) =>
|
||||||
|
secret.sourceEnv ? secret.sourceEnv === env : secret.env === env
|
||||||
|
).length ?? 0
|
||||||
|
);
|
||||||
},
|
},
|
||||||
[secrets]
|
[secrets]
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import {
|
|||||||
faVault
|
faVault
|
||||||
} 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 { Link, Outlet } from "@tanstack/react-router";
|
import { Link, Outlet, useLocation } from "@tanstack/react-router";
|
||||||
import { motion } from "framer-motion";
|
import { motion } from "framer-motion";
|
||||||
|
|
||||||
import { Badge, Lottie, Menu, MenuGroup, MenuItem } from "@app/components/v2";
|
import { Badge, Lottie, Menu, MenuGroup, MenuItem } from "@app/components/v2";
|
||||||
@@ -31,6 +31,7 @@ export const SecretManagerLayout = () => {
|
|||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const workspaceId = currentWorkspace?.id || "";
|
const workspaceId = currentWorkspace?.id || "";
|
||||||
const projectSlug = currentWorkspace?.slug || "";
|
const projectSlug = currentWorkspace?.slug || "";
|
||||||
|
const location = useLocation();
|
||||||
|
|
||||||
const { data: secretApprovalReqCount } = useGetSecretApprovalRequestCount({
|
const { data: secretApprovalReqCount } = useGetSecretApprovalRequestCount({
|
||||||
workspaceId
|
workspaceId
|
||||||
@@ -84,7 +85,14 @@ export const SecretManagerLayout = () => {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{({ isActive }) => (
|
{({ isActive }) => (
|
||||||
<MenuItem isSelected={isActive}>
|
<MenuItem
|
||||||
|
isSelected={
|
||||||
|
isActive ||
|
||||||
|
location.pathname.startsWith(
|
||||||
|
`/projects/secret-management/${currentWorkspace.id}/secrets`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
>
|
||||||
<div className="mx-1 flex gap-2">
|
<div className="mx-1 flex gap-2">
|
||||||
<div className="w-6">
|
<div className="w-6">
|
||||||
<FontAwesomeIcon icon={faVault} />
|
<FontAwesomeIcon icon={faVault} />
|
||||||
|
|||||||
@@ -66,6 +66,7 @@ import { CommitForm } from "./components/CommitForm";
|
|||||||
import { CreateSecretForm } from "./components/CreateSecretForm";
|
import { CreateSecretForm } from "./components/CreateSecretForm";
|
||||||
import { DynamicSecretListView } from "./components/DynamicSecretListView";
|
import { DynamicSecretListView } from "./components/DynamicSecretListView";
|
||||||
import { EnvironmentTabs } from "./components/EnvironmentTabs";
|
import { EnvironmentTabs } from "./components/EnvironmentTabs";
|
||||||
|
import { FolderBreadCrumbs } from "./components/FolderBreadCrumbs";
|
||||||
import { FolderListView } from "./components/FolderListView";
|
import { FolderListView } from "./components/FolderListView";
|
||||||
import { PitDrawer } from "./components/PitDrawer";
|
import { PitDrawer } from "./components/PitDrawer";
|
||||||
import { SecretDropzone } from "./components/SecretDropzone";
|
import { SecretDropzone } from "./components/SecretDropzone";
|
||||||
@@ -107,7 +108,7 @@ const Page = () => {
|
|||||||
const { permission } = useProjectPermission();
|
const { permission } = useProjectPermission();
|
||||||
const { mutateAsync: createCommit } = useCreateCommit();
|
const { mutateAsync: createCommit } = useCreateCommit();
|
||||||
|
|
||||||
const tableRef = useRef<HTMLDivElement>(null);
|
const tableRef = useRef<HTMLTableElement>(null);
|
||||||
|
|
||||||
const [isVisible, setIsVisible] = useState(false);
|
const [isVisible, setIsVisible] = useState(false);
|
||||||
const { isBatchMode, pendingChanges } = useBatchMode();
|
const { isBatchMode, pendingChanges } = useBatchMode();
|
||||||
@@ -251,7 +252,8 @@ const Page = () => {
|
|||||||
const {
|
const {
|
||||||
data,
|
data,
|
||||||
isPending: isDetailsLoading,
|
isPending: isDetailsLoading,
|
||||||
isFetching: isDetailsFetching
|
isFetching: isDetailsFetching,
|
||||||
|
isFetched
|
||||||
} = useGetProjectSecretsDetails({
|
} = useGetProjectSecretsDetails({
|
||||||
environment,
|
environment,
|
||||||
projectId: workspaceId,
|
projectId: workspaceId,
|
||||||
@@ -272,6 +274,18 @@ const Page = () => {
|
|||||||
tags: filter.tags
|
tags: filter.tags
|
||||||
});
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// if switching tabs in a folder path that doesn't exist in a separate env we navigate to the root
|
||||||
|
if (!data && isFetched) {
|
||||||
|
navigate({
|
||||||
|
search: (prev) => ({
|
||||||
|
...prev,
|
||||||
|
secretPath: "/"
|
||||||
|
})
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [data, isFetched]);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
imports,
|
imports,
|
||||||
folders,
|
folders,
|
||||||
@@ -493,7 +507,8 @@ const Page = () => {
|
|||||||
minWidth: 100,
|
minWidth: 100,
|
||||||
maxWidth: tableRef.current
|
maxWidth: tableRef.current
|
||||||
? tableRef.current.clientWidth - 148 // ensure value column can't collapse completely
|
? tableRef.current.clientWidth - 148 // ensure value column can't collapse completely
|
||||||
: 800
|
: 800,
|
||||||
|
ref: tableRef
|
||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -767,6 +782,7 @@ const Page = () => {
|
|||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<SecretV2MigrationSection />
|
<SecretV2MigrationSection />
|
||||||
|
<FolderBreadCrumbs secretPath={secretPath} />
|
||||||
<EnvironmentTabs secretPath={secretPath} />
|
<EnvironmentTabs secretPath={secretPath} />
|
||||||
{!isRollbackMode ? (
|
{!isRollbackMode ? (
|
||||||
<>
|
<>
|
||||||
|
|||||||
+28
-22
@@ -86,7 +86,6 @@ const DEFAULT_FILTER_STATE = {
|
|||||||
[RowType.SecretRotation]: false
|
[RowType.SecretRotation]: false
|
||||||
};
|
};
|
||||||
|
|
||||||
const TABLE_WIDTH_OFFSET = 17;
|
|
||||||
const COL_WIDTH_OFFSET = 220;
|
const COL_WIDTH_OFFSET = 220;
|
||||||
|
|
||||||
export const CompareEnvironments = ({ secretPath }: Props) => {
|
export const CompareEnvironments = ({ secretPath }: Props) => {
|
||||||
@@ -190,7 +189,6 @@ export const CompareEnvironments = ({ secretPath }: Props) => {
|
|||||||
totalSecretCount,
|
totalSecretCount,
|
||||||
totalDynamicSecretCount,
|
totalDynamicSecretCount,
|
||||||
totalSecretRotationCount,
|
totalSecretRotationCount,
|
||||||
totalImportCount,
|
|
||||||
totalCount = 0,
|
totalCount = 0,
|
||||||
totalUniqueFoldersInPage,
|
totalUniqueFoldersInPage,
|
||||||
totalUniqueSecretsInPage,
|
totalUniqueSecretsInPage,
|
||||||
@@ -241,15 +239,15 @@ export const CompareEnvironments = ({ secretPath }: Props) => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const [tableWidth, setTableWidth] = useState(0);
|
const [tableWidth, setTableWidth] = useState(0);
|
||||||
|
const tableRef = useRef<HTMLTableElement>(null);
|
||||||
const tableRef = useRef<HTMLDivElement>(null);
|
|
||||||
|
|
||||||
const { handleMouseDown, isResizing, colWidth } = useResizableColWidth({
|
const { handleMouseDown, isResizing, colWidth } = useResizableColWidth({
|
||||||
initialWidth: 320,
|
initialWidth: 320,
|
||||||
minWidth: 160,
|
minWidth: 160,
|
||||||
maxWidth: tableRef.current
|
maxWidth: tableRef.current
|
||||||
? tableRef.current.clientWidth - COL_WIDTH_OFFSET // ensure value column can't collapse completely
|
? tableRef.current.clientWidth - COL_WIDTH_OFFSET // ensure value column can't collapse completely
|
||||||
: 800
|
: 800,
|
||||||
|
ref: tableRef
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleToggleRowType = useCallback(
|
const handleToggleRowType = useCallback(
|
||||||
@@ -268,19 +266,21 @@ export const CompareEnvironments = ({ secretPath }: Props) => {
|
|||||||
const isTableFiltered = isFilteredByResources;
|
const isTableFiltered = isFilteredByResources;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const resizeObserver = new ResizeObserver((entries) => {
|
const element = tableRef.current;
|
||||||
// eslint-disable-next-line no-restricted-syntax
|
if (!element) return;
|
||||||
for (const entry of entries) {
|
|
||||||
setTableWidth(entry.contentRect.width - TABLE_WIDTH_OFFSET);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (tableRef.current) {
|
const handleResize = () => {
|
||||||
resizeObserver.observe(tableRef.current);
|
setTableWidth(element.clientWidth - 1);
|
||||||
}
|
};
|
||||||
|
|
||||||
return () => resizeObserver.disconnect();
|
const resizeObserver = new ResizeObserver(handleResize);
|
||||||
}, []);
|
resizeObserver.observe(element);
|
||||||
|
|
||||||
|
// eslint-disable-next-line consistent-return
|
||||||
|
return () => {
|
||||||
|
resizeObserver.disconnect();
|
||||||
|
};
|
||||||
|
}, [tableRef]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
// scott: this is reverse to fix z-indexing bug of dropdown with sticky table cols; couldn't resolve with flex-col
|
// scott: this is reverse to fix z-indexing bug of dropdown with sticky table cols; couldn't resolve with flex-col
|
||||||
@@ -292,7 +292,6 @@ export const CompareEnvironments = ({ secretPath }: Props) => {
|
|||||||
dynamicSecretCount={totalDynamicSecretCount}
|
dynamicSecretCount={totalDynamicSecretCount}
|
||||||
secretCount={totalSecretCount}
|
secretCount={totalSecretCount}
|
||||||
folderCount={totalFolderCount}
|
folderCount={totalFolderCount}
|
||||||
importCount={totalImportCount}
|
|
||||||
secretRotationCount={totalSecretRotationCount}
|
secretRotationCount={totalSecretRotationCount}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
@@ -304,8 +303,9 @@ export const CompareEnvironments = ({ secretPath }: Props) => {
|
|||||||
onChangePerPage={handlePerPageChange}
|
onChangePerPage={handlePerPageChange}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<div ref={tableRef} className="thin-scrollbar flex flex-1 flex-col overflow-y-auto">
|
<div className="thin-scrollbar flex flex-1 flex-col overflow-y-auto">
|
||||||
<TableContainer
|
<TableContainer
|
||||||
|
ref={tableRef}
|
||||||
className={twMerge(
|
className={twMerge(
|
||||||
"mt-4 flex flex-1 flex-col border-mineshaft-500 bg-mineshaft-700",
|
"mt-4 flex flex-1 flex-col border-mineshaft-500 bg-mineshaft-700",
|
||||||
!isTableEmpty && "rounded-b-none"
|
!isTableEmpty && "rounded-b-none"
|
||||||
@@ -347,11 +347,11 @@ export const CompareEnvironments = ({ secretPath }: Props) => {
|
|||||||
<div className="pointer-events-none absolute -right-[0.02rem] top-[0.67rem] z-30">
|
<div className="pointer-events-none absolute -right-[0.02rem] top-[0.67rem] z-30">
|
||||||
<div className="h-5 w-0.5 rounded-[1.5px] bg-gray-400 opacity-50" />
|
<div className="h-5 w-0.5 rounded-[1.5px] bg-gray-400 opacity-50" />
|
||||||
</div>
|
</div>
|
||||||
<div className="flex h-full items-center border-b-2 border-r border-mineshaft-500 bg-mineshaft-700 bg-clip-padding p-0 px-4 py-2.5">
|
<div className="flex h-full items-center border-b-2 border-r border-mineshaft-500 bg-mineshaft-700 bg-clip-padding p-0 px-4 py-2.5 text-sm normal-case">
|
||||||
Name
|
Name
|
||||||
<IconButton
|
<IconButton
|
||||||
variant="plain"
|
variant="plain"
|
||||||
className="ml-2"
|
className="ml-1 mt-[0.1rem]"
|
||||||
ariaLabel="sort"
|
ariaLabel="sort"
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
setOrderDirection((prev) =>
|
setOrderDirection((prev) =>
|
||||||
@@ -362,6 +362,7 @@ export const CompareEnvironments = ({ secretPath }: Props) => {
|
|||||||
}
|
}
|
||||||
>
|
>
|
||||||
<FontAwesomeIcon
|
<FontAwesomeIcon
|
||||||
|
className="h-3"
|
||||||
icon={orderDirection === "asc" ? faArrowDown : faArrowUp}
|
icon={orderDirection === "asc" ? faArrowDown : faArrowUp}
|
||||||
/>
|
/>
|
||||||
</IconButton>
|
</IconButton>
|
||||||
@@ -380,7 +381,7 @@ export const CompareEnvironments = ({ secretPath }: Props) => {
|
|||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className={twMerge(
|
className={twMerge(
|
||||||
"flex h-full w-full items-center justify-center gap-x-2 border-b-2 border-mineshaft-500 bg-mineshaft-700 p-0 px-4 py-3 text-center",
|
"flex h-full w-full items-center justify-center gap-x-2 border-b-2 border-mineshaft-500 bg-mineshaft-700 p-0 px-4 py-2.5 text-center text-sm normal-case",
|
||||||
index < compareEnvironments.length - 1 && "border-r"
|
index < compareEnvironments.length - 1 && "border-r"
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
@@ -388,7 +389,12 @@ export const CompareEnvironments = ({ secretPath }: Props) => {
|
|||||||
{missingKeyCount > 0 && (
|
{missingKeyCount > 0 && (
|
||||||
<Tooltip
|
<Tooltip
|
||||||
className="max-w-none lowercase"
|
className="max-w-none lowercase"
|
||||||
content={`${missingKeyCount} secrets missing\n compared to other environments`}
|
content={
|
||||||
|
<>
|
||||||
|
{missingKeyCount} secret{missingKeyCount > 1 ? "s" : ""} missing
|
||||||
|
compared to other environments on this page
|
||||||
|
</>
|
||||||
|
}
|
||||||
>
|
>
|
||||||
<Badge
|
<Badge
|
||||||
variant="primary"
|
variant="primary"
|
||||||
|
|||||||
+18
-13
@@ -2,7 +2,7 @@ import { faEye, faEyeSlash, faInfoCircle, faRotate } from "@fortawesome/free-sol
|
|||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
import { twMerge } from "tailwind-merge";
|
import { twMerge } from "tailwind-merge";
|
||||||
|
|
||||||
import { Button, TableContainer, Tag, Td, Tooltip, Tr } from "@app/components/v2";
|
import { IconButton, TableContainer, Tag, Td, Tooltip, Tr } from "@app/components/v2";
|
||||||
import { Blur } from "@app/components/v2/Blur";
|
import { Blur } from "@app/components/v2/Blur";
|
||||||
import { InfisicalSecretInput } from "@app/components/v2/InfisicalSecretInput";
|
import { InfisicalSecretInput } from "@app/components/v2/InfisicalSecretInput";
|
||||||
import { SECRET_ROTATION_MAP } from "@app/helpers/secretRotationsV2";
|
import { SECRET_ROTATION_MAP } from "@app/helpers/secretRotationsV2";
|
||||||
@@ -70,13 +70,13 @@ export const SecretRotationRow = ({
|
|||||||
<Td
|
<Td
|
||||||
colSpan={totalCols}
|
colSpan={totalCols}
|
||||||
style={{ minWidth: tableWidth, maxWidth: tableWidth }}
|
style={{ minWidth: tableWidth, maxWidth: tableWidth }}
|
||||||
className="sticky left-0 bg-mineshaft-800 bg-clip-padding px-0 py-0"
|
className="sticky left-0 bg-clip-padding px-0 py-0"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
style={{ minWidth: tableWidth, maxWidth: tableWidth }}
|
style={{ minWidth: tableWidth, maxWidth: tableWidth }}
|
||||||
className="sticky left-0 bg-mineshaft-800 bg-clip-padding px-0 py-0"
|
className="sticky left-0 bg-clip-padding px-0 py-0"
|
||||||
>
|
>
|
||||||
<div className="flex !h-[40px] items-center justify-between gap-x-2 px-4">
|
<div className="flex !h-[40px] items-center justify-between gap-x-2 bg-mineshaft-800 px-4">
|
||||||
<div className="w-full">
|
<div className="w-full">
|
||||||
<div className="flex w-full flex-wrap items-center gap-x-2.5">
|
<div className="flex w-full flex-wrap items-center gap-x-2.5">
|
||||||
<span>{envName}</span>
|
<span>{envName}</span>
|
||||||
@@ -95,17 +95,22 @@ export const SecretRotationRow = ({
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Button
|
<Tooltip
|
||||||
variant="plain"
|
side="left"
|
||||||
colorSchema="secondary"
|
content={isSecretVisible ? "Hide Values" : "Reveal Values"}
|
||||||
leftIcon={<FontAwesomeIcon icon={isSecretVisible ? faEyeSlash : faEye} />}
|
|
||||||
onClick={() => setIsSecretVisible.toggle()}
|
|
||||||
>
|
>
|
||||||
{isSecretVisible ? "Hide Values" : "Reveal Values"}
|
<IconButton
|
||||||
</Button>
|
variant="plain"
|
||||||
|
colorSchema="secondary"
|
||||||
|
ariaLabel={isSecretVisible ? "Hide Values" : "Reveal Values"}
|
||||||
|
onClick={() => setIsSecretVisible.toggle()}
|
||||||
|
>
|
||||||
|
<FontAwesomeIcon icon={isSecretVisible ? faEyeSlash : faEye} />
|
||||||
|
</IconButton>
|
||||||
|
</Tooltip>
|
||||||
</div>
|
</div>
|
||||||
<TableContainer className="rounded-none border-0">
|
<TableContainer className="rounded-none border-0">
|
||||||
<table className="secret-table w-full border-b-0">
|
<table className="secret-table w-full border-b-0 !bg-mineshaft-900">
|
||||||
<tbody className="!last:border-b-0 w-full border-t-2 border-mineshaft-600">
|
<tbody className="!last:border-b-0 w-full border-t-2 border-mineshaft-600">
|
||||||
{secrets.map((secret, index) => {
|
{secrets.map((secret, index) => {
|
||||||
return (
|
return (
|
||||||
@@ -119,7 +124,7 @@ export const SecretRotationRow = ({
|
|||||||
// eslint-disable-next-line react/no-array-index-key
|
// eslint-disable-next-line react/no-array-index-key
|
||||||
key={`rotation-secret-${secretRotation.id}-${index}`}
|
key={`rotation-secret-${secretRotation.id}-${index}`}
|
||||||
>
|
>
|
||||||
<tr className="hover:bg-mineshaft-700/70">
|
<tr className="hover:bg-mineshaft-800/50">
|
||||||
<td
|
<td
|
||||||
style={{
|
style={{
|
||||||
width: colWidth
|
width: colWidth
|
||||||
|
|||||||
+18
-13
@@ -9,7 +9,7 @@ import {
|
|||||||
} 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 { Button, TableContainer, Td, Tooltip, Tr } from "@app/components/v2";
|
import { IconButton, TableContainer, Td, Tooltip, Tr } from "@app/components/v2";
|
||||||
import { useProjectPermission } from "@app/context";
|
import { useProjectPermission } from "@app/context";
|
||||||
import {
|
import {
|
||||||
ProjectPermissionSecretActions,
|
ProjectPermissionSecretActions,
|
||||||
@@ -122,16 +122,16 @@ export const SecretRow = ({
|
|||||||
<Td
|
<Td
|
||||||
colSpan={totalCols}
|
colSpan={totalCols}
|
||||||
style={{ minWidth: tableWidth, maxWidth: tableWidth }}
|
style={{ minWidth: tableWidth, maxWidth: tableWidth }}
|
||||||
className="sticky left-0 bg-mineshaft-800 bg-clip-padding px-0 py-0"
|
className="sticky left-0 bg-clip-padding px-0 py-0"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
style={{ minWidth: tableWidth, maxWidth: tableWidth }}
|
style={{ minWidth: tableWidth, maxWidth: tableWidth }}
|
||||||
className="sticky left-0 bg-mineshaft-800 bg-clip-padding px-0 py-0"
|
className="sticky left-0 bg-clip-padding px-0 py-0"
|
||||||
>
|
>
|
||||||
<TableContainer className="rounded-none border-0">
|
<TableContainer className="rounded-none border-0">
|
||||||
<table className="secret-table">
|
<table className="secret-table bg-mineshaft-900">
|
||||||
<thead>
|
<thead>
|
||||||
<tr className="h-10 border-b-2 border-mineshaft-600">
|
<tr className="h-10 border-b-2 border-mineshaft-600 bg-mineshaft-800">
|
||||||
<th
|
<th
|
||||||
style={{
|
style={{
|
||||||
width: colWidth
|
width: colWidth
|
||||||
@@ -143,15 +143,20 @@ export const SecretRow = ({
|
|||||||
<th style={{ padding: "0.5rem 1rem" }} className="border-none">
|
<th style={{ padding: "0.5rem 1rem" }} className="border-none">
|
||||||
Value
|
Value
|
||||||
</th>
|
</th>
|
||||||
<div className="absolute right-3 top-[1px] ml-auto mr-1 mt-1 w-min">
|
<div className="absolute right-3 top-[4px] ml-auto mr-1 mt-1 w-min">
|
||||||
<Button
|
<Tooltip
|
||||||
variant="plain"
|
side="left"
|
||||||
colorSchema="secondary"
|
content={isSecretVisible ? "Hide Values" : "Reveal Values"}
|
||||||
leftIcon={<FontAwesomeIcon icon={isSecretVisible ? faEyeSlash : faEye} />}
|
|
||||||
onClick={() => setIsSecretVisible.toggle()}
|
|
||||||
>
|
>
|
||||||
{isSecretVisible ? "Hide Values" : "Reveal Values"}
|
<IconButton
|
||||||
</Button>
|
variant="plain"
|
||||||
|
colorSchema="secondary"
|
||||||
|
ariaLabel={isSecretVisible ? "Hide Values" : "Reveal Values"}
|
||||||
|
onClick={() => setIsSecretVisible.toggle()}
|
||||||
|
>
|
||||||
|
<FontAwesomeIcon icon={isSecretVisible ? faEyeSlash : faEye} />
|
||||||
|
</IconButton>
|
||||||
|
</Tooltip>
|
||||||
</div>
|
</div>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
|
|||||||
+1
-1
@@ -150,7 +150,7 @@ export const EnvironmentTabs = ({ secretPath }: Props) => {
|
|||||||
<DropdownMenuContent
|
<DropdownMenuContent
|
||||||
className="thin-scrollbar max-h-[70vh] overflow-y-auto"
|
className="thin-scrollbar max-h-[70vh] overflow-y-auto"
|
||||||
sideOffset={2}
|
sideOffset={2}
|
||||||
align="end"
|
align="center"
|
||||||
>
|
>
|
||||||
<DropdownMenuLabel>Environments</DropdownMenuLabel>
|
<DropdownMenuLabel>Environments</DropdownMenuLabel>
|
||||||
<div className="thin-scrollbar max-h-[40vh] overflow-auto">
|
<div className="thin-scrollbar max-h-[40vh] overflow-auto">
|
||||||
|
|||||||
+52
@@ -0,0 +1,52 @@
|
|||||||
|
import { faFolderOpen } from "@fortawesome/free-solid-svg-icons";
|
||||||
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
|
import { useNavigate } from "@tanstack/react-router";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
secretPath: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const FolderBreadCrumbs = ({ secretPath = "/" }: Props) => {
|
||||||
|
const navigate = useNavigate({
|
||||||
|
from: "/projects/secret-management/$projectId/secrets/$envSlug"
|
||||||
|
});
|
||||||
|
|
||||||
|
const onFolderCrumbClick = (index: number) => {
|
||||||
|
const newSecPath = `/${secretPath.split("/").filter(Boolean).slice(0, index).join("/")}`;
|
||||||
|
if (secretPath === newSecPath) return;
|
||||||
|
navigate({
|
||||||
|
search: (prev) => ({ ...prev, secretPath: newSecPath })
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="mb-3 flex flex-wrap items-center gap-x-2 gap-y-3">
|
||||||
|
<div
|
||||||
|
className="breadcrumb relative z-20 border-solid border-mineshaft-600 bg-mineshaft-800 py-1 pl-5 pr-2 text-sm hover:bg-mineshaft-600"
|
||||||
|
onClick={() => onFolderCrumbClick(0)}
|
||||||
|
onKeyDown={() => null}
|
||||||
|
role="button"
|
||||||
|
tabIndex={0}
|
||||||
|
>
|
||||||
|
<FontAwesomeIcon icon={faFolderOpen} className="text-primary-700" />
|
||||||
|
</div>
|
||||||
|
{(secretPath || "")
|
||||||
|
.split("/")
|
||||||
|
.filter(Boolean)
|
||||||
|
.map((path, index, arr) => (
|
||||||
|
<div
|
||||||
|
key={`secret-path-${index + 1}`}
|
||||||
|
className={`breadcrumb relative z-20 ${
|
||||||
|
index + 1 === arr.length ? "cursor-default" : "cursor-pointer"
|
||||||
|
} border-solid border-mineshaft-600 py-1 pl-5 pr-2 text-sm text-mineshaft-200`}
|
||||||
|
onClick={() => onFolderCrumbClick(index + 1)}
|
||||||
|
onKeyDown={() => null}
|
||||||
|
role="button"
|
||||||
|
tabIndex={0}
|
||||||
|
>
|
||||||
|
{path}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
export { FolderBreadCrumbs } from "./FolderBreadCrumbs";
|
||||||
Reference in New Issue
Block a user