diff --git a/frontend/src/components/permissions/AccessTree/AccessTree.tsx b/frontend/src/components/permissions/AccessTree/AccessTree.tsx
index 8376ae72f..265e87623 100644
--- a/frontend/src/components/permissions/AccessTree/AccessTree.tsx
+++ b/frontend/src/components/permissions/AccessTree/AccessTree.tsx
@@ -1,7 +1,7 @@
import { useCallback, useEffect, useState } from "react";
import { MongoAbility, MongoQuery } from "@casl/ability";
import {
- faArrowsToDot,
+ faAnglesUp,
faArrowsUpDownLeftRight,
faArrowUpRightFromSquare,
faDownLeftAndUpRightToCenter,
@@ -241,7 +241,7 @@ const AccessTreeContent = ({ permissions }: AccessTreeProps) => {
-
+
diff --git a/frontend/src/components/permissions/AccessTree/hooks/index.ts b/frontend/src/components/permissions/AccessTree/hooks/index.ts
index 9a0292fb6..8651ea543 100644
--- a/frontend/src/components/permissions/AccessTree/hooks/index.ts
+++ b/frontend/src/components/permissions/AccessTree/hooks/index.ts
@@ -88,21 +88,18 @@ export const useAccessTree = (
if (!environmentsFolders || !permissions || !environmentsFolders[environment]) return;
const { folders } = environmentsFolders[environment];
-
setTotalFolderCount(folders.length);
- const groupedFolders: Record = {};
+ const searchPathFolder = folders.find((folder) => folder.path === searchPath);
const filteredFolders = folders.filter((folder) => {
- if (folder.path.startsWith(searchPath)) {
+ if (folder.path === searchPath) {
return true;
}
if (
- searchPath.startsWith(folder.path) &&
- (folder.path === "/" ||
- searchPath === folder.path ||
- searchPath.indexOf("/", folder.path.length) === folder.path.length)
+ folder.path.startsWith(searchPath) &&
+ (searchPath === "/" || folder.path.charAt(searchPath.length) === "/")
) {
return true;
}
@@ -110,11 +107,17 @@ export const useAccessTree = (
return false;
});
+ const rootFolder = searchPathFolder || filteredFolders.find((f) => f.path === "/");
+
+ const groupedFolders: Record = {};
+
filteredFolders.forEach((folder) => {
const parentId = folder.parentId || "";
+
if (!groupedFolders[parentId]) {
groupedFolders[parentId] = [];
}
+
groupedFolders[parentId].push(folder);
});
@@ -129,7 +132,18 @@ export const useAccessTree = (
};
});
- setLevelFolderMap(newLevelFolderMap);
+ if (rootFolder) {
+ setLevelFolderMap({
+ ...newLevelFolderMap,
+ __rootFolderId: {
+ folders: [rootFolder],
+ visibleCount: 1,
+ hasMore: false
+ }
+ });
+ } else {
+ setLevelFolderMap(newLevelFolderMap);
+ }
}, [permissions, environmentsFolders, environment, subject, secretName, searchPath]);
useEffect(() => {
@@ -151,11 +165,15 @@ export const useAccessTree = (
const actionRuleMap = getSubjectActionRuleMap(subject, permissions);
const visibleFolders: TSecretFolderWithPath[] = [];
-
- Object.values(levelFolderMap).forEach((levelData) => {
- visibleFolders.push(...levelData.folders.slice(0, levelData.visibleCount));
+ Object.entries(levelFolderMap).forEach(([key, levelData]) => {
+ if (key !== "__rootFolderId") {
+ visibleFolders.push(...levelData.folders.slice(0, levelData.visibleCount));
+ }
});
+ // eslint-disable-next-line no-underscore-dangle
+ const rootFolder = levelFolderMap.__rootFolderId?.folders[0];
+
const folderNodes = visibleFolders.map((folder) =>
createFolderNode({
folder,
@@ -167,10 +185,45 @@ export const useAccessTree = (
})
);
- const folderEdges = folderNodes.map(({ data: folder }) => {
- const actions = Object.values(folder.actions);
+ const folderEdges: Edge[] = [];
+ if (rootFolder) {
+ const rootFolderNode = folderNodes.find(
+ (node) => node.data.id === rootFolder.id || node.data.path === rootFolder.path
+ );
+
+ if (rootFolderNode) {
+ const rootActions = Object.values(rootFolderNode.data.actions);
+ let rootAccess: PermissionAccess;
+
+ if (Object.values(rootActions).some((action) => action === PermissionAccess.Full)) {
+ rootAccess = PermissionAccess.Full;
+ } else if (
+ Object.values(rootActions).some((action) => action === PermissionAccess.Partial)
+ ) {
+ rootAccess = PermissionAccess.Partial;
+ } else {
+ rootAccess = PermissionAccess.None;
+ }
+
+ folderEdges.push(
+ createBaseEdge({
+ source: roleNode.id,
+ target: rootFolderNode.id,
+ access: rootAccess
+ })
+ );
+ }
+ }
+
+ folderNodes.forEach(({ data: folder }) => {
+ if (rootFolder && (folder.id === rootFolder.id || folder.path === rootFolder.path)) {
+ return;
+ }
+
+ const actions = Object.values(folder.actions);
let access: PermissionAccess;
+
if (Object.values(actions).some((action) => action === PermissionAccess.Full)) {
access = PermissionAccess.Full;
} else if (Object.values(actions).some((action) => action === PermissionAccess.Partial)) {
@@ -179,16 +232,20 @@ export const useAccessTree = (
access = PermissionAccess.None;
}
- return createBaseEdge({
- source: folder.parentId ?? roleNode.id,
- target: folder.id,
- access
- });
+ folderEdges.push(
+ createBaseEdge({
+ source: folder.parentId ?? roleNode.id,
+ target: folder.id,
+ access
+ })
+ );
});
const addMoreButtons: Node[] = [];
Object.entries(levelFolderMap).forEach(([parentId, levelData]) => {
+ if (parentId === "__rootFolderId") return;
+
const key = parentId === "null" ? null : parentId;
if (key && levelData.hasMore) {
diff --git a/frontend/src/components/permissions/AccessTree/nodes/FolderNode/components/AccessTreeSecretPathInput.tsx b/frontend/src/components/permissions/AccessTree/nodes/FolderNode/components/AccessTreeSecretPathInput.tsx
index 0e3c8777e..1702d1214 100644
--- a/frontend/src/components/permissions/AccessTree/nodes/FolderNode/components/AccessTreeSecretPathInput.tsx
+++ b/frontend/src/components/permissions/AccessTree/nodes/FolderNode/components/AccessTreeSecretPathInput.tsx
@@ -1,5 +1,5 @@
-import { useRef, useState } from "react";
-import { faSearch, faTimes } from "@fortawesome/free-solid-svg-icons";
+import { useEffect, useRef, useState } from "react";
+import { faSearch } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { twMerge } from "tailwind-merge";
@@ -34,6 +34,12 @@ export const AccessTreeSecretPathInput = ({
}, 200);
};
+ useEffect(() => {
+ if (!isFocused) {
+ setIsExpanded(false);
+ }
+ }, [isFocused]);
+
const focusInput = () => {
const inputElement = inputRef.current?.querySelector("input");
if (inputElement) {
@@ -69,7 +75,7 @@ export const AccessTreeSecretPathInput = ({
}
}}
>
-
+
) : (
diff --git a/frontend/src/components/v2/SecretPathInput/SecretPathInput.tsx b/frontend/src/components/v2/SecretPathInput/SecretPathInput.tsx
index 31d0ff72c..c82e492e8 100644
--- a/frontend/src/components/v2/SecretPathInput/SecretPathInput.tsx
+++ b/frontend/src/components/v2/SecretPathInput/SecretPathInput.tsx
@@ -48,7 +48,6 @@ export const SecretPathInput = ({
}, [propValue]);
useEffect(() => {
- // update secret path if input is valid
if (
(debouncedInputValue.length > 0 &&
debouncedInputValue[debouncedInputValue.length - 1] === "/") ||
@@ -59,7 +58,6 @@ export const SecretPathInput = ({
}, [debouncedInputValue]);
useEffect(() => {
- // filter suggestions based on matching
const searchFragment = debouncedInputValue.split("/").pop() || "";
const filteredSuggestions = folders
.filter((suggestionEntry) =>
@@ -78,7 +76,6 @@ export const SecretPathInput = ({
const validPaths = inputValue.split("/");
validPaths.pop();
- // removed trailing slash
const newValue = `${validPaths.join("/")}/${suggestions[selectedIndex]}`;
onChange?.(newValue);
setInputValue(newValue);
@@ -102,7 +99,6 @@ export const SecretPathInput = ({
};
const handleInputChange = (e: any) => {
- // propagate event to react-hook-form onChange
if (onChange) {
onChange(e.target.value);
}