mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-07 15:27:46 +00:00
Allow access tree relative path graph on path filter
This commit is contained in:
@@ -1,7 +1,7 @@
|
|||||||
import { useCallback, useEffect, useState } from "react";
|
import { useCallback, useEffect, useState } from "react";
|
||||||
import { MongoAbility, MongoQuery } from "@casl/ability";
|
import { MongoAbility, MongoQuery } from "@casl/ability";
|
||||||
import {
|
import {
|
||||||
faArrowsToDot,
|
faAnglesUp,
|
||||||
faArrowsUpDownLeftRight,
|
faArrowsUpDownLeftRight,
|
||||||
faArrowUpRightFromSquare,
|
faArrowUpRightFromSquare,
|
||||||
faDownLeftAndUpRightToCenter,
|
faDownLeftAndUpRightToCenter,
|
||||||
@@ -241,7 +241,7 @@ const AccessTreeContent = ({ permissions }: AccessTreeProps) => {
|
|||||||
</ControlButton>
|
</ControlButton>
|
||||||
<ControlButton onClick={goToRootNode}>
|
<ControlButton onClick={goToRootNode}>
|
||||||
<Tooltip position="right" content="Go to root folder">
|
<Tooltip position="right" content="Go to root folder">
|
||||||
<FontAwesomeIcon icon={faArrowsToDot} />
|
<FontAwesomeIcon icon={faAnglesUp} />
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</ControlButton>
|
</ControlButton>
|
||||||
</Controls>
|
</Controls>
|
||||||
|
|||||||
@@ -88,21 +88,18 @@ export const useAccessTree = (
|
|||||||
if (!environmentsFolders || !permissions || !environmentsFolders[environment]) return;
|
if (!environmentsFolders || !permissions || !environmentsFolders[environment]) return;
|
||||||
|
|
||||||
const { folders } = environmentsFolders[environment];
|
const { folders } = environmentsFolders[environment];
|
||||||
|
|
||||||
setTotalFolderCount(folders.length);
|
setTotalFolderCount(folders.length);
|
||||||
|
|
||||||
const groupedFolders: Record<string, TSecretFolderWithPath[]> = {};
|
const searchPathFolder = folders.find((folder) => folder.path === searchPath);
|
||||||
|
|
||||||
const filteredFolders = folders.filter((folder) => {
|
const filteredFolders = folders.filter((folder) => {
|
||||||
if (folder.path.startsWith(searchPath)) {
|
if (folder.path === searchPath) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
searchPath.startsWith(folder.path) &&
|
folder.path.startsWith(searchPath) &&
|
||||||
(folder.path === "/" ||
|
(searchPath === "/" || folder.path.charAt(searchPath.length) === "/")
|
||||||
searchPath === folder.path ||
|
|
||||||
searchPath.indexOf("/", folder.path.length) === folder.path.length)
|
|
||||||
) {
|
) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -110,11 +107,17 @@ export const useAccessTree = (
|
|||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const rootFolder = searchPathFolder || filteredFolders.find((f) => f.path === "/");
|
||||||
|
|
||||||
|
const groupedFolders: Record<string, TSecretFolderWithPath[]> = {};
|
||||||
|
|
||||||
filteredFolders.forEach((folder) => {
|
filteredFolders.forEach((folder) => {
|
||||||
const parentId = folder.parentId || "";
|
const parentId = folder.parentId || "";
|
||||||
|
|
||||||
if (!groupedFolders[parentId]) {
|
if (!groupedFolders[parentId]) {
|
||||||
groupedFolders[parentId] = [];
|
groupedFolders[parentId] = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
groupedFolders[parentId].push(folder);
|
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]);
|
}, [permissions, environmentsFolders, environment, subject, secretName, searchPath]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -151,11 +165,15 @@ export const useAccessTree = (
|
|||||||
const actionRuleMap = getSubjectActionRuleMap(subject, permissions);
|
const actionRuleMap = getSubjectActionRuleMap(subject, permissions);
|
||||||
|
|
||||||
const visibleFolders: TSecretFolderWithPath[] = [];
|
const visibleFolders: TSecretFolderWithPath[] = [];
|
||||||
|
Object.entries(levelFolderMap).forEach(([key, levelData]) => {
|
||||||
Object.values(levelFolderMap).forEach((levelData) => {
|
if (key !== "__rootFolderId") {
|
||||||
visibleFolders.push(...levelData.folders.slice(0, levelData.visibleCount));
|
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) =>
|
const folderNodes = visibleFolders.map((folder) =>
|
||||||
createFolderNode({
|
createFolderNode({
|
||||||
folder,
|
folder,
|
||||||
@@ -167,10 +185,45 @@ export const useAccessTree = (
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
const folderEdges = folderNodes.map(({ data: folder }) => {
|
const folderEdges: Edge[] = [];
|
||||||
const actions = Object.values(folder.actions);
|
|
||||||
|
|
||||||
|
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;
|
let access: PermissionAccess;
|
||||||
|
|
||||||
if (Object.values(actions).some((action) => action === PermissionAccess.Full)) {
|
if (Object.values(actions).some((action) => action === PermissionAccess.Full)) {
|
||||||
access = PermissionAccess.Full;
|
access = PermissionAccess.Full;
|
||||||
} else if (Object.values(actions).some((action) => action === PermissionAccess.Partial)) {
|
} else if (Object.values(actions).some((action) => action === PermissionAccess.Partial)) {
|
||||||
@@ -179,16 +232,20 @@ export const useAccessTree = (
|
|||||||
access = PermissionAccess.None;
|
access = PermissionAccess.None;
|
||||||
}
|
}
|
||||||
|
|
||||||
return createBaseEdge({
|
folderEdges.push(
|
||||||
source: folder.parentId ?? roleNode.id,
|
createBaseEdge({
|
||||||
target: folder.id,
|
source: folder.parentId ?? roleNode.id,
|
||||||
access
|
target: folder.id,
|
||||||
});
|
access
|
||||||
|
})
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
const addMoreButtons: Node[] = [];
|
const addMoreButtons: Node[] = [];
|
||||||
|
|
||||||
Object.entries(levelFolderMap).forEach(([parentId, levelData]) => {
|
Object.entries(levelFolderMap).forEach(([parentId, levelData]) => {
|
||||||
|
if (parentId === "__rootFolderId") return;
|
||||||
|
|
||||||
const key = parentId === "null" ? null : parentId;
|
const key = parentId === "null" ? null : parentId;
|
||||||
|
|
||||||
if (key && levelData.hasMore) {
|
if (key && levelData.hasMore) {
|
||||||
|
|||||||
+9
-3
@@ -1,5 +1,5 @@
|
|||||||
import { useRef, useState } from "react";
|
import { useEffect, useRef, useState } from "react";
|
||||||
import { faSearch, faTimes } from "@fortawesome/free-solid-svg-icons";
|
import { faSearch } 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";
|
||||||
|
|
||||||
@@ -34,6 +34,12 @@ export const AccessTreeSecretPathInput = ({
|
|||||||
}, 200);
|
}, 200);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!isFocused) {
|
||||||
|
setIsExpanded(false);
|
||||||
|
}
|
||||||
|
}, [isFocused]);
|
||||||
|
|
||||||
const focusInput = () => {
|
const focusInput = () => {
|
||||||
const inputElement = inputRef.current?.querySelector("input");
|
const inputElement = inputRef.current?.querySelector("input");
|
||||||
if (inputElement) {
|
if (inputElement) {
|
||||||
@@ -69,7 +75,7 @@ export const AccessTreeSecretPathInput = ({
|
|||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<FontAwesomeIcon icon={faTimes} />
|
<FontAwesomeIcon icon={faSearch} />
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<Tooltip position="bottom" content="Search paths">
|
<Tooltip position="bottom" content="Search paths">
|
||||||
|
|||||||
@@ -48,7 +48,6 @@ export const SecretPathInput = ({
|
|||||||
}, [propValue]);
|
}, [propValue]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// update secret path if input is valid
|
|
||||||
if (
|
if (
|
||||||
(debouncedInputValue.length > 0 &&
|
(debouncedInputValue.length > 0 &&
|
||||||
debouncedInputValue[debouncedInputValue.length - 1] === "/") ||
|
debouncedInputValue[debouncedInputValue.length - 1] === "/") ||
|
||||||
@@ -59,7 +58,6 @@ export const SecretPathInput = ({
|
|||||||
}, [debouncedInputValue]);
|
}, [debouncedInputValue]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// filter suggestions based on matching
|
|
||||||
const searchFragment = debouncedInputValue.split("/").pop() || "";
|
const searchFragment = debouncedInputValue.split("/").pop() || "";
|
||||||
const filteredSuggestions = folders
|
const filteredSuggestions = folders
|
||||||
.filter((suggestionEntry) =>
|
.filter((suggestionEntry) =>
|
||||||
@@ -78,7 +76,6 @@ export const SecretPathInput = ({
|
|||||||
const validPaths = inputValue.split("/");
|
const validPaths = inputValue.split("/");
|
||||||
validPaths.pop();
|
validPaths.pop();
|
||||||
|
|
||||||
// removed trailing slash
|
|
||||||
const newValue = `${validPaths.join("/")}/${suggestions[selectedIndex]}`;
|
const newValue = `${validPaths.join("/")}/${suggestions[selectedIndex]}`;
|
||||||
onChange?.(newValue);
|
onChange?.(newValue);
|
||||||
setInputValue(newValue);
|
setInputValue(newValue);
|
||||||
@@ -102,7 +99,6 @@ export const SecretPathInput = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleInputChange = (e: any) => {
|
const handleInputChange = (e: any) => {
|
||||||
// propagate event to react-hook-form onChange
|
|
||||||
if (onChange) {
|
if (onChange) {
|
||||||
onChange(e.target.value);
|
onChange(e.target.value);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user