mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-06 23:27:14 +00:00
improvement: improve dashboard filter behavior and design
This commit is contained in:
@@ -11,12 +11,12 @@ import {
|
|||||||
faArrowRightToBracket,
|
faArrowRightToBracket,
|
||||||
faArrowUp,
|
faArrowUp,
|
||||||
faFileImport,
|
faFileImport,
|
||||||
|
faFilter,
|
||||||
faFingerprint,
|
faFingerprint,
|
||||||
faFolder,
|
faFolder,
|
||||||
faFolderBlank,
|
faFolderBlank,
|
||||||
faFolderPlus,
|
faFolderPlus,
|
||||||
faKey,
|
faKey,
|
||||||
faList,
|
|
||||||
faPlus,
|
faPlus,
|
||||||
faRotate
|
faRotate
|
||||||
} from "@fortawesome/free-solid-svg-icons";
|
} from "@fortawesome/free-solid-svg-icons";
|
||||||
@@ -96,7 +96,12 @@ import { OrderByDirection } from "@app/hooks/api/generic/types";
|
|||||||
import { useUpdateFolderBatch } from "@app/hooks/api/secretFolders/queries";
|
import { useUpdateFolderBatch } from "@app/hooks/api/secretFolders/queries";
|
||||||
import { TUpdateFolderBatchDTO } from "@app/hooks/api/secretFolders/types";
|
import { TUpdateFolderBatchDTO } from "@app/hooks/api/secretFolders/types";
|
||||||
import { TSecretRotationV2 } from "@app/hooks/api/secretRotationsV2";
|
import { TSecretRotationV2 } from "@app/hooks/api/secretRotationsV2";
|
||||||
import { SecretType, SecretV3RawSanitized, TSecretFolder } from "@app/hooks/api/types";
|
import {
|
||||||
|
SecretType,
|
||||||
|
SecretV3RawSanitized,
|
||||||
|
TSecretFolder,
|
||||||
|
WorkspaceEnv
|
||||||
|
} from "@app/hooks/api/types";
|
||||||
import { ProjectVersion } from "@app/hooks/api/workspace/types";
|
import { ProjectVersion } from "@app/hooks/api/workspace/types";
|
||||||
import {
|
import {
|
||||||
useDynamicSecretOverview,
|
useDynamicSecretOverview,
|
||||||
@@ -144,11 +149,11 @@ type Filter = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const DEFAULT_FILTER_STATE = {
|
const DEFAULT_FILTER_STATE = {
|
||||||
[RowType.Folder]: true,
|
[RowType.Folder]: false,
|
||||||
[RowType.DynamicSecret]: true,
|
[RowType.DynamicSecret]: false,
|
||||||
[RowType.Secret]: true,
|
[RowType.Secret]: false,
|
||||||
[RowType.Import]: true,
|
[RowType.Import]: false,
|
||||||
[RowType.SecretRotation]: true
|
[RowType.SecretRotation]: false
|
||||||
};
|
};
|
||||||
|
|
||||||
const DEFAULT_COLLAPSED_HEADER_HEIGHT = 120;
|
const DEFAULT_COLLAPSED_HEADER_HEIGHT = 120;
|
||||||
@@ -265,11 +270,8 @@ export const OverviewPage = () => {
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
const [visibleEnvs, setVisibleEnvs] = useState(userAvailableEnvs);
|
const [filteredEnvs, setFilteredEnvs] = useState<WorkspaceEnv[]>([]);
|
||||||
|
const visibleEnvs = filteredEnvs.length ? filteredEnvs : userAvailableEnvs;
|
||||||
useEffect(() => {
|
|
||||||
setVisibleEnvs(userAvailableEnvs);
|
|
||||||
}, [userAvailableEnvs]);
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
secretImports,
|
secretImports,
|
||||||
@@ -282,6 +284,7 @@ export const OverviewPage = () => {
|
|||||||
environments: (userAvailableEnvs || []).map(({ slug }) => slug)
|
environments: (userAvailableEnvs || []).map(({ slug }) => slug)
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const isFilteredByResources = Object.values(filter).some(Boolean);
|
||||||
const { isPending: isOverviewLoading, data: overview } = useGetProjectSecretsOverview(
|
const { isPending: isOverviewLoading, data: overview } = useGetProjectSecretsOverview(
|
||||||
{
|
{
|
||||||
projectId: workspaceId,
|
projectId: workspaceId,
|
||||||
@@ -289,11 +292,11 @@ export const OverviewPage = () => {
|
|||||||
secretPath,
|
secretPath,
|
||||||
orderDirection,
|
orderDirection,
|
||||||
orderBy,
|
orderBy,
|
||||||
includeFolders: filter.folder,
|
includeFolders: isFilteredByResources ? filter.folder : true,
|
||||||
includeDynamicSecrets: filter.dynamic,
|
includeDynamicSecrets: isFilteredByResources ? filter.dynamic : true,
|
||||||
includeSecrets: filter.secret,
|
includeSecrets: isFilteredByResources ? filter.secret : true,
|
||||||
includeImports: filter.import,
|
includeImports: isFilteredByResources ? filter.import : true,
|
||||||
includeSecretRotations: filter.rotation,
|
includeSecretRotations: isFilteredByResources ? filter.rotation : true,
|
||||||
search: debouncedSearchFilter,
|
search: debouncedSearchFilter,
|
||||||
limit,
|
limit,
|
||||||
offset
|
offset
|
||||||
@@ -529,10 +532,10 @@ export const OverviewPage = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleEnvSelect = (envId: string) => {
|
const handleEnvSelect = (envId: string) => {
|
||||||
if (visibleEnvs.map((env) => env.id).includes(envId)) {
|
if (filteredEnvs.map((env) => env.id).includes(envId)) {
|
||||||
setVisibleEnvs(visibleEnvs.filter((env) => env.id !== envId));
|
setFilteredEnvs(filteredEnvs.filter((env) => env.id !== envId));
|
||||||
} else {
|
} else {
|
||||||
setVisibleEnvs(visibleEnvs.concat(userAvailableEnvs.filter((env) => env.id === envId)));
|
setFilteredEnvs(filteredEnvs.concat(userAvailableEnvs.filter((env) => env.id === envId)));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -792,11 +795,11 @@ export const OverviewPage = () => {
|
|||||||
envNames: string[],
|
envNames: string[],
|
||||||
envs: { environment: string; importedBy: ProjectSecretsImportedBy[] }[]
|
envs: { environment: string; importedBy: ProjectSecretsImportedBy[] }[]
|
||||||
): ProjectSecretsImportedBy[] => {
|
): ProjectSecretsImportedBy[] => {
|
||||||
const filteredEnvs = envs.filter((env) => envNames.includes(env.environment));
|
const environments = envs.filter((env) => envNames.includes(env.environment));
|
||||||
|
|
||||||
if (filteredEnvs.length === 0) return [];
|
if (environments.length === 0) return [];
|
||||||
|
|
||||||
const allImportedBy = filteredEnvs.flatMap((env) => env.importedBy);
|
const allImportedBy = environments.flatMap((env) => env.importedBy);
|
||||||
const groupedBySlug: Record<string, ProjectSecretsImportedBy[]> = {};
|
const groupedBySlug: Record<string, ProjectSecretsImportedBy[]> = {};
|
||||||
|
|
||||||
allImportedBy.forEach((item) => {
|
allImportedBy.forEach((item) => {
|
||||||
@@ -902,9 +905,7 @@ export const OverviewPage = () => {
|
|||||||
|
|
||||||
const isTableEmpty = totalCount === 0;
|
const isTableEmpty = totalCount === 0;
|
||||||
|
|
||||||
const isTableFiltered =
|
const isTableFiltered = isFilteredByResources || filteredEnvs.length > 0;
|
||||||
Boolean(Object.values(filter).filter((enabled) => !enabled).length) ||
|
|
||||||
userAvailableEnvs.length !== visibleEnvs.length;
|
|
||||||
|
|
||||||
if (!isProjectV3)
|
if (!isProjectV3)
|
||||||
return (
|
return (
|
||||||
@@ -969,26 +970,42 @@ export const OverviewPage = () => {
|
|||||||
<div className="mt-4 flex items-center justify-between">
|
<div className="mt-4 flex items-center justify-between">
|
||||||
<FolderBreadCrumbs secretPath={secretPath} onResetSearch={handleResetSearch} />
|
<FolderBreadCrumbs secretPath={secretPath} onResetSearch={handleResetSearch} />
|
||||||
<div className="flex flex-row items-center justify-center space-x-2">
|
<div className="flex flex-row items-center justify-center space-x-2">
|
||||||
|
{isTableFiltered && (
|
||||||
|
<Button
|
||||||
|
variant="plain"
|
||||||
|
colorSchema="secondary"
|
||||||
|
onClick={() => {
|
||||||
|
setFilteredEnvs([]);
|
||||||
|
setFilter(DEFAULT_FILTER_STATE);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Clear Filters
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
{userAvailableEnvs.length > 0 && (
|
{userAvailableEnvs.length > 0 && (
|
||||||
<DropdownMenu>
|
<DropdownMenu>
|
||||||
<DropdownMenuTrigger asChild>
|
<DropdownMenuTrigger asChild>
|
||||||
<IconButton
|
<Button
|
||||||
ariaLabel="Environments"
|
|
||||||
variant="plain"
|
|
||||||
size="sm"
|
size="sm"
|
||||||
|
variant="outline_bg"
|
||||||
className={twMerge(
|
className={twMerge(
|
||||||
"flex h-10 w-11 items-center justify-center overflow-hidden border border-mineshaft-600 bg-mineshaft-800 p-0 transition-all hover:border-primary/60 hover:bg-primary/10",
|
"flex h-[2.5rem]",
|
||||||
isTableFiltered && "border-primary/50 text-primary"
|
isTableFiltered && "border-primary/40 bg-primary/10"
|
||||||
)}
|
)}
|
||||||
|
leftIcon={
|
||||||
|
<FontAwesomeIcon
|
||||||
|
icon={faFilter}
|
||||||
|
className={isTableFiltered ? "text-primary/80" : undefined}
|
||||||
|
/>
|
||||||
|
}
|
||||||
>
|
>
|
||||||
<Tooltip content="Choose visible environments" className="mb-2">
|
Filters
|
||||||
<FontAwesomeIcon icon={faList} />
|
</Button>
|
||||||
</Tooltip>
|
|
||||||
</IconButton>
|
|
||||||
</DropdownMenuTrigger>
|
</DropdownMenuTrigger>
|
||||||
<DropdownMenuContent
|
<DropdownMenuContent
|
||||||
className="thin-scrollbar max-h-[70vh] overflow-y-auto"
|
className="thin-scrollbar max-h-[70vh] overflow-y-auto"
|
||||||
align="end"
|
align="end"
|
||||||
|
sideOffset={2}
|
||||||
>
|
>
|
||||||
{/* <DropdownMenuItem className="px-1.5" asChild>
|
{/* <DropdownMenuItem className="px-1.5" asChild>
|
||||||
<Button
|
<Button
|
||||||
@@ -1070,11 +1087,11 @@ export const OverviewPage = () => {
|
|||||||
<span>Secrets</span>
|
<span>Secrets</span>
|
||||||
</div>
|
</div>
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
<DropdownMenuLabel>Choose visible environments</DropdownMenuLabel>
|
<DropdownMenuLabel>Filter by Environment</DropdownMenuLabel>
|
||||||
{userAvailableEnvs.map((availableEnv) => {
|
{userAvailableEnvs.map((availableEnv) => {
|
||||||
const { id: envId, name } = availableEnv;
|
const { id: envId, name } = availableEnv;
|
||||||
|
|
||||||
const isEnvSelected = visibleEnvs.map((env) => env.id).includes(envId);
|
const isEnvSelected = filteredEnvs.map((env) => env.id).includes(envId);
|
||||||
return (
|
return (
|
||||||
<DropdownMenuItem
|
<DropdownMenuItem
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
@@ -1082,7 +1099,6 @@ export const OverviewPage = () => {
|
|||||||
handleEnvSelect(envId);
|
handleEnvSelect(envId);
|
||||||
}}
|
}}
|
||||||
key={envId}
|
key={envId}
|
||||||
disabled={visibleEnvs?.length === 1}
|
|
||||||
icon={isEnvSelected && <FontAwesomeIcon icon={faCheckCircle} />}
|
icon={isEnvSelected && <FontAwesomeIcon icon={faCheckCircle} />}
|
||||||
iconPos="right"
|
iconPos="right"
|
||||||
>
|
>
|
||||||
@@ -1405,13 +1421,6 @@ export const OverviewPage = () => {
|
|||||||
className="bg-mineshaft-700"
|
className="bg-mineshaft-700"
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{userAvailableEnvs.length > 0 && visibleEnvs.length === 0 && (
|
|
||||||
<Tr>
|
|
||||||
<Td colSpan={visibleEnvs.length + 1}>
|
|
||||||
<EmptyState title="You have no visible environments" iconSize="3x" />
|
|
||||||
</Td>
|
|
||||||
</Tr>
|
|
||||||
)}
|
|
||||||
{userAvailableEnvs.length === 0 && (
|
{userAvailableEnvs.length === 0 && (
|
||||||
<Tr>
|
<Tr>
|
||||||
<Td colSpan={visibleEnvs.length + 1}>
|
<Td colSpan={visibleEnvs.length + 1}>
|
||||||
@@ -1439,12 +1448,12 @@ export const OverviewPage = () => {
|
|||||||
</Td>
|
</Td>
|
||||||
</Tr>
|
</Tr>
|
||||||
)}
|
)}
|
||||||
{isTableEmpty && !isOverviewLoading && visibleEnvs.length > 0 && (
|
{isTableEmpty && !isOverviewLoading && isTableFiltered && (
|
||||||
<Tr>
|
<Tr>
|
||||||
<Td colSpan={visibleEnvs.length + 1}>
|
<Td colSpan={visibleEnvs.length + 1}>
|
||||||
<EmptyState
|
<EmptyState
|
||||||
title={
|
title={
|
||||||
debouncedSearchFilter
|
isTableFiltered
|
||||||
? "No secret found for your search, add one now"
|
? "No secret found for your search, add one now"
|
||||||
: "Let's add some secrets"
|
: "Let's add some secrets"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -212,11 +212,11 @@ const Page = () => {
|
|||||||
searchFilter: (routerQueryParams.search as string) || "",
|
searchFilter: (routerQueryParams.search as string) || "",
|
||||||
// these should always be on by default for the UI, they will be disabled for the query below based off permissions
|
// these should always be on by default for the UI, they will be disabled for the query below based off permissions
|
||||||
include: {
|
include: {
|
||||||
[RowType.Folder]: true,
|
[RowType.Folder]: false,
|
||||||
[RowType.Import]: true,
|
[RowType.Import]: false,
|
||||||
[RowType.DynamicSecret]: true,
|
[RowType.DynamicSecret]: false,
|
||||||
[RowType.Secret]: true,
|
[RowType.Secret]: false,
|
||||||
[RowType.SecretRotation]: true
|
[RowType.SecretRotation]: false
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -242,6 +242,7 @@ const Page = () => {
|
|||||||
}
|
}
|
||||||
}, [currentWorkspace, environment]);
|
}, [currentWorkspace, environment]);
|
||||||
|
|
||||||
|
const isResourceTypeFiltered = Object.values(filter.include).some(Boolean);
|
||||||
const {
|
const {
|
||||||
data,
|
data,
|
||||||
isPending: isDetailsLoading,
|
isPending: isDetailsLoading,
|
||||||
@@ -255,12 +256,14 @@ const Page = () => {
|
|||||||
orderBy,
|
orderBy,
|
||||||
search: debouncedSearchFilter,
|
search: debouncedSearchFilter,
|
||||||
orderDirection,
|
orderDirection,
|
||||||
includeImports: canReadSecretImports && filter.include.import,
|
includeImports: canReadSecretImports && (isResourceTypeFiltered ? filter.include.import : true),
|
||||||
includeFolders: filter.include.folder,
|
includeFolders: isResourceTypeFiltered ? filter.include.folder : true,
|
||||||
viewSecretValue: canReadSecretValue,
|
viewSecretValue: canReadSecretValue,
|
||||||
includeDynamicSecrets: canReadDynamicSecret && filter.include.dynamic,
|
includeDynamicSecrets:
|
||||||
includeSecrets: canReadSecret && filter.include.secret,
|
canReadDynamicSecret && (isResourceTypeFiltered ? filter.include.dynamic : true),
|
||||||
includeSecretRotations: canReadSecretRotations && filter.include.rotation,
|
includeSecrets: canReadSecret && (isResourceTypeFiltered ? filter.include.secret : true),
|
||||||
|
includeSecretRotations:
|
||||||
|
canReadSecretRotations && (isResourceTypeFiltered ? filter.include.rotation : true),
|
||||||
tags: filter.tags
|
tags: filter.tags
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -769,6 +772,19 @@ const Page = () => {
|
|||||||
isPITEnabled={isPITEnabled}
|
isPITEnabled={isPITEnabled}
|
||||||
hasPathPolicies={hasPathPolicies}
|
hasPathPolicies={hasPathPolicies}
|
||||||
onRequestAccess={(params) => handlePopUpOpen("requestAccess", params)}
|
onRequestAccess={(params) => handlePopUpOpen("requestAccess", params)}
|
||||||
|
onClearFilters={() =>
|
||||||
|
setFilter((prev) => ({
|
||||||
|
...prev,
|
||||||
|
tags: {},
|
||||||
|
include: {
|
||||||
|
secret: false,
|
||||||
|
import: false,
|
||||||
|
dynamic: false,
|
||||||
|
rotation: false,
|
||||||
|
folder: false
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
className={twMerge(
|
className={twMerge(
|
||||||
|
|||||||
+63
-43
@@ -136,6 +136,7 @@ type Props = {
|
|||||||
isPITEnabled: boolean;
|
isPITEnabled: boolean;
|
||||||
onRequestAccess: (actions: ProjectPermissionActions[]) => void;
|
onRequestAccess: (actions: ProjectPermissionActions[]) => void;
|
||||||
hasPathPolicies: boolean;
|
hasPathPolicies: boolean;
|
||||||
|
onClearFilters: () => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ActionBar = ({
|
export const ActionBar = ({
|
||||||
@@ -159,7 +160,8 @@ export const ActionBar = ({
|
|||||||
isPITEnabled = false,
|
isPITEnabled = false,
|
||||||
usedBySecretSyncs,
|
usedBySecretSyncs,
|
||||||
onRequestAccess,
|
onRequestAccess,
|
||||||
hasPathPolicies
|
hasPathPolicies,
|
||||||
|
onClearFilters
|
||||||
}: Props) => {
|
}: Props) => {
|
||||||
const { handlePopUpOpen, handlePopUpToggle, handlePopUpClose, popUp } = usePopUp([
|
const { handlePopUpOpen, handlePopUpToggle, handlePopUpClose, popUp } = usePopUp([
|
||||||
"addFolder",
|
"addFolder",
|
||||||
@@ -661,6 +663,9 @@ export const ActionBar = ({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const isTableFiltered =
|
||||||
|
Object.values(filter.tags).some(Boolean) || Object.values(filter.include).some(Boolean);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="mt-4 flex items-center space-x-2">
|
<div className="mt-4 flex items-center space-x-2">
|
||||||
@@ -676,18 +681,22 @@ export const ActionBar = ({
|
|||||||
<div>
|
<div>
|
||||||
<DropdownMenu>
|
<DropdownMenu>
|
||||||
<DropdownMenuTrigger asChild>
|
<DropdownMenuTrigger asChild>
|
||||||
<IconButton
|
<Button
|
||||||
|
size="sm"
|
||||||
variant="outline_bg"
|
variant="outline_bg"
|
||||||
ariaLabel="Download"
|
|
||||||
className={twMerge(
|
className={twMerge(
|
||||||
"transition-all",
|
"flex h-[2.5rem]",
|
||||||
(Object.keys(filter.tags).length ||
|
isTableFiltered && "border-primary/40 bg-primary/10"
|
||||||
Object.values(filter.include).filter((include) => !include).length) &&
|
|
||||||
"border-primary/50 text-primary"
|
|
||||||
)}
|
)}
|
||||||
|
leftIcon={
|
||||||
|
<FontAwesomeIcon
|
||||||
|
icon={faFilter}
|
||||||
|
className={isTableFiltered ? "text-primary/80" : undefined}
|
||||||
|
/>
|
||||||
|
}
|
||||||
>
|
>
|
||||||
<FontAwesomeIcon icon={faFilter} />
|
Filters
|
||||||
</IconButton>
|
</Button>
|
||||||
</DropdownMenuTrigger>
|
</DropdownMenuTrigger>
|
||||||
<DropdownMenuContent align="end" className="p-0">
|
<DropdownMenuContent align="end" className="p-0">
|
||||||
<DropdownMenuGroup>Filter By</DropdownMenuGroup>
|
<DropdownMenuGroup>Filter By</DropdownMenuGroup>
|
||||||
@@ -762,51 +771,62 @@ export const ActionBar = ({
|
|||||||
<span>Secrets</span>
|
<span>Secrets</span>
|
||||||
</div>
|
</div>
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
<DropdownSubMenu>
|
{Boolean(tags.length) && (
|
||||||
<DropdownSubMenuTrigger
|
<DropdownSubMenu>
|
||||||
iconPos="right"
|
<DropdownSubMenuTrigger
|
||||||
icon={<FontAwesomeIcon icon={faChevronRight} size="sm" />}
|
iconPos="right"
|
||||||
>
|
icon={<FontAwesomeIcon icon={faChevronRight} size="sm" />}
|
||||||
Tags
|
>
|
||||||
</DropdownSubMenuTrigger>
|
Tags
|
||||||
<DropdownSubMenuContent className="thin-scrollbar max-h-[20rem] overflow-y-auto rounded-l-none">
|
</DropdownSubMenuTrigger>
|
||||||
<DropdownMenuLabel className="sticky top-0 bg-mineshaft-900">
|
<DropdownSubMenuContent className="thin-scrollbar max-h-[20rem] overflow-y-auto rounded-l-none">
|
||||||
Apply Tags to Filter Secrets
|
<DropdownMenuLabel className="sticky top-0 bg-mineshaft-900">
|
||||||
</DropdownMenuLabel>
|
Apply Tags to Filter Secrets
|
||||||
{tags.map(({ id, slug, color }) => (
|
</DropdownMenuLabel>
|
||||||
<DropdownMenuItem
|
{tags.map(({ id, slug, color }) => (
|
||||||
onClick={(evt) => {
|
<DropdownMenuItem
|
||||||
evt.preventDefault();
|
onClick={(evt) => {
|
||||||
onToggleTagFilter(slug);
|
evt.preventDefault();
|
||||||
}}
|
onToggleTagFilter(slug);
|
||||||
key={id}
|
}}
|
||||||
icon={filter?.tags[slug] && <FontAwesomeIcon icon={faCheckCircle} />}
|
key={id}
|
||||||
iconPos="right"
|
icon={filter?.tags[slug] && <FontAwesomeIcon icon={faCheckCircle} />}
|
||||||
>
|
iconPos="right"
|
||||||
<div className="flex items-center">
|
>
|
||||||
<div
|
<div className="flex items-center">
|
||||||
className="mr-2 h-2 w-2 rounded-full"
|
<div
|
||||||
style={{ background: color || "#bec2c8" }}
|
className="mr-2 h-2 w-2 rounded-full"
|
||||||
/>
|
style={{ background: color || "#bec2c8" }}
|
||||||
{slug}
|
/>
|
||||||
</div>
|
{slug}
|
||||||
</DropdownMenuItem>
|
</div>
|
||||||
))}
|
</DropdownMenuItem>
|
||||||
</DropdownSubMenuContent>
|
))}
|
||||||
</DropdownSubMenu>
|
</DropdownSubMenuContent>
|
||||||
|
</DropdownSubMenu>
|
||||||
|
)}
|
||||||
</DropdownMenuContent>
|
</DropdownMenuContent>
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
</div>
|
</div>
|
||||||
|
{isTableFiltered && (
|
||||||
|
<Button variant="plain" colorSchema="secondary" onClick={onClearFilters}>
|
||||||
|
Clear Filters
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
<div className="flex-grow" />
|
||||||
<div>
|
<div>
|
||||||
{isProtectedBranch && (
|
{isProtectedBranch && (
|
||||||
<Tooltip content={`Protected by policy ${protectedBranchPolicyName}`}>
|
<Tooltip content={`Protected by policy ${protectedBranchPolicyName}`}>
|
||||||
<IconButton variant="outline_bg" ariaLabel="protected">
|
<IconButton
|
||||||
|
variant="outline_bg"
|
||||||
|
className="border-primary/40 bg-primary/10"
|
||||||
|
ariaLabel="protected"
|
||||||
|
>
|
||||||
<FontAwesomeIcon icon={faLock} className="text-primary" />
|
<FontAwesomeIcon icon={faLock} className="text-primary" />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="flex-grow" />
|
|
||||||
<div>
|
<div>
|
||||||
<IconButton variant="outline_bg" ariaLabel="Download" onClick={handleSecretDownload}>
|
<IconButton variant="outline_bg" ariaLabel="Download" onClick={handleSecretDownload}>
|
||||||
<FontAwesomeIcon icon={faDownload} />
|
<FontAwesomeIcon icon={faDownload} />
|
||||||
|
|||||||
Reference in New Issue
Block a user