mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-06 04:26:43 +00:00
feat: enhance navigation and URL state management in SecretDashboardPage and input components
This commit is contained in:
@@ -2,6 +2,7 @@ import { forwardRef, TextareaHTMLAttributes, useCallback, useMemo, useRef, useSt
|
|||||||
import { faFolder, faKey, faLayerGroup, faSearch } from "@fortawesome/free-solid-svg-icons";
|
import { faFolder, faKey, faLayerGroup, faSearch } from "@fortawesome/free-solid-svg-icons";
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
import * as Popover from "@radix-ui/react-popover";
|
import * as Popover from "@radix-ui/react-popover";
|
||||||
|
import { useNavigate } from "@tanstack/react-router";
|
||||||
|
|
||||||
import { ROUTE_PATHS } from "@app/const/routes";
|
import { ROUTE_PATHS } from "@app/const/routes";
|
||||||
import { useProject } from "@app/context";
|
import { useProject } from "@app/context";
|
||||||
@@ -81,6 +82,7 @@ export const InfisicalSecretInput = forwardRef<HTMLTextAreaElement, Props>(
|
|||||||
) => {
|
) => {
|
||||||
const { currentProject } = useProject();
|
const { currentProject } = useProject();
|
||||||
const projectId = currentProject?.id || "";
|
const projectId = currentProject?.id || "";
|
||||||
|
const navigate = useNavigate({ from: ROUTE_PATHS.SecretManager.SecretDashboardPage.path });
|
||||||
|
|
||||||
const [debouncedValue] = useDebounce(value, 100);
|
const [debouncedValue] = useDebounce(value, 100);
|
||||||
|
|
||||||
@@ -310,37 +312,49 @@ export const InfisicalSecretInput = forwardRef<HTMLTextAreaElement, Props>(
|
|||||||
|
|
||||||
const handleClickSegment = useCallback(
|
const handleClickSegment = useCallback(
|
||||||
(segment: string, allSegments: string[]) => {
|
(segment: string, allSegments: string[]) => {
|
||||||
// Single segment: search in current environment and path
|
|
||||||
if (allSegments.length === 1) {
|
if (allSegments.length === 1) {
|
||||||
const currentEnv = propEnvironment || "";
|
navigate({
|
||||||
const currentPath = propSecretPath || "/";
|
search: (prev) => ({
|
||||||
const encodedPath = encodeURIComponent(currentPath);
|
...prev,
|
||||||
const url = ROUTE_PATHS.SecretManager.SecretDashboardPage.path
|
search: segment,
|
||||||
.replace("$projectId", projectId)
|
filterBy: "secret"
|
||||||
.replace("$envSlug", currentEnv);
|
})
|
||||||
window.open(
|
});
|
||||||
`${url}?secretPath=${encodedPath}&search=${encodeURIComponent(segment)}&filterBy=secret`,
|
|
||||||
"_blank",
|
|
||||||
"noopener,noreferrer"
|
|
||||||
);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Multiple segments: first is env, last is secret, middle are folders
|
|
||||||
const environmentSlug = allSegments[0];
|
const environmentSlug = allSegments[0];
|
||||||
const secretName = allSegments[allSegments.length - 1];
|
const secretName = allSegments[allSegments.length - 1];
|
||||||
const folderPath = allSegments.length > 2 ? `/${allSegments.slice(1, -1).join("/")}` : "/";
|
let folderPath = "/";
|
||||||
const encodedPath = encodeURIComponent(folderPath);
|
|
||||||
const url = ROUTE_PATHS.SecretManager.SecretDashboardPage.path
|
if (allSegments.length > 2) {
|
||||||
.replace("$projectId", projectId)
|
const pathSegments = allSegments.slice(1, -1);
|
||||||
.replace("$envSlug", environmentSlug);
|
for (let i = 0; i < pathSegments.length; i += 1) {
|
||||||
window.open(
|
const pathSegment = pathSegments[i];
|
||||||
`${url}?secretPath=${encodedPath}&search=${encodeURIComponent(secretName)}&filterBy=secret`,
|
folderPath += `${pathSegment}`;
|
||||||
"_blank",
|
if (pathSegment === segment) {
|
||||||
"noopener,noreferrer"
|
folderPath += "/";
|
||||||
);
|
break;
|
||||||
|
}
|
||||||
|
folderPath += "/";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
navigate({
|
||||||
|
to: ROUTE_PATHS.SecretManager.SecretDashboardPage.path,
|
||||||
|
params: {
|
||||||
|
projectId,
|
||||||
|
envSlug: environmentSlug
|
||||||
|
},
|
||||||
|
search: (prev) => ({
|
||||||
|
...prev,
|
||||||
|
secretPath: segment === environmentSlug ? "/" : folderPath,
|
||||||
|
search: segment === secretName ? secretName : prev.search,
|
||||||
|
filterBy: segment === secretName ? "secret" : prev.filterBy
|
||||||
|
})
|
||||||
|
});
|
||||||
},
|
},
|
||||||
[projectId, propEnvironment, propSecretPath]
|
[navigate, projectId]
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -53,10 +53,16 @@ const syntaxHighlight = (
|
|||||||
} ${shouldShowHoverStyle ? "cursor-pointer underline decoration-yellow-400" : ""}`}
|
} ${shouldShowHoverStyle ? "cursor-pointer underline decoration-yellow-400" : ""}`}
|
||||||
onMouseEnter={() => onHoverPart?.(segmentKey)}
|
onMouseEnter={() => onHoverPart?.(segmentKey)}
|
||||||
onMouseLeave={() => onHoverPart?.("")}
|
onMouseLeave={() => onHoverPart?.("")}
|
||||||
onClick={(e) => {
|
onMouseDown={(e) => {
|
||||||
if (isCmdOrCtrlPressed) {
|
if (isCmdOrCtrlPressed) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
if (isCmdOrCtrlPressed) {
|
||||||
|
e.preventDefault();
|
||||||
onClickSegment?.(segment, parts);
|
onClickSegment?.(segment, parts);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -475,34 +475,73 @@ const Page = () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const handleTagToggle = useCallback(
|
const handleTagToggle = useCallback(
|
||||||
(tagSlug: string) =>
|
(tagSlug: string) => {
|
||||||
setFilter((state) => {
|
setFilter((state) => {
|
||||||
const isTagPresent = Boolean(state.tags?.[tagSlug]);
|
const isTagPresent = Boolean(state.tags?.[tagSlug]);
|
||||||
const newTagFilter = { ...state.tags };
|
const newTagFilter = { ...state.tags };
|
||||||
if (isTagPresent) delete newTagFilter[tagSlug];
|
if (isTagPresent) delete newTagFilter[tagSlug];
|
||||||
else newTagFilter[tagSlug] = true;
|
else newTagFilter[tagSlug] = true;
|
||||||
|
|
||||||
|
// Update URL to match filter state
|
||||||
|
const tagsList = Object.keys(newTagFilter).filter((tag) => newTagFilter[tag]);
|
||||||
|
navigate({
|
||||||
|
search: (prev) => ({
|
||||||
|
...prev,
|
||||||
|
tags: tagsList.length > 0 ? tagsList.join(",") : ""
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
return { ...state, tags: newTagFilter };
|
return { ...state, tags: newTagFilter };
|
||||||
}),
|
});
|
||||||
[]
|
},
|
||||||
|
[navigate]
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleToggleRowType = useCallback(
|
const handleToggleRowType = useCallback(
|
||||||
(rowType: RowType) =>
|
(rowType: RowType) => {
|
||||||
setFilter((state) => {
|
setFilter((state) => {
|
||||||
|
const newInclude = {
|
||||||
|
...state.include,
|
||||||
|
[rowType]: !state.include[rowType]
|
||||||
|
};
|
||||||
|
|
||||||
|
// Update URL to match filter state
|
||||||
|
const filterByList: string[] = [];
|
||||||
|
if (newInclude[RowType.Folder]) filterByList.push("folder");
|
||||||
|
if (newInclude[RowType.Import]) filterByList.push("import");
|
||||||
|
if (newInclude[RowType.DynamicSecret]) filterByList.push("dynamic");
|
||||||
|
if (newInclude[RowType.Secret]) filterByList.push("secret");
|
||||||
|
if (newInclude[RowType.SecretRotation]) filterByList.push("rotation");
|
||||||
|
|
||||||
|
navigate({
|
||||||
|
search: (prev) => ({
|
||||||
|
...prev,
|
||||||
|
filterBy: filterByList.length > 0 ? filterByList.join(",") : ""
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
include: {
|
include: newInclude
|
||||||
...state.include,
|
|
||||||
[rowType]: !state.include[rowType]
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
}),
|
});
|
||||||
[]
|
},
|
||||||
|
[navigate]
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleSearchChange = useCallback(
|
const handleSearchChange = useCallback(
|
||||||
(searchFilter: string) => setFilter((state) => ({ ...state, searchFilter })),
|
(searchFilter: string) => {
|
||||||
[]
|
setFilter((state) => ({ ...state, searchFilter }));
|
||||||
|
|
||||||
|
// Update URL to match filter state
|
||||||
|
navigate({
|
||||||
|
search: (prev) => ({
|
||||||
|
...prev,
|
||||||
|
search: searchFilter || ""
|
||||||
|
})
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[navigate]
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleToggleVisibility = useCallback(() => setIsVisible((state) => !state), []);
|
const handleToggleVisibility = useCallback(() => setIsVisible((state) => !state), []);
|
||||||
@@ -564,13 +603,6 @@ const Page = () => {
|
|||||||
include: includeFilter
|
include: includeFilter
|
||||||
}));
|
}));
|
||||||
setDebouncedSearchFilter(routerQueryParams.search as string);
|
setDebouncedSearchFilter(routerQueryParams.search as string);
|
||||||
// this is a temp workaround until we fully transition state to query params,
|
|
||||||
navigate({
|
|
||||||
search: (state) => {
|
|
||||||
const { search, tags: qTags, filterBy: qFilterBy, ...query } = state;
|
|
||||||
return query;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}, [routerQueryParams.search, routerQueryParams.tags, routerQueryParams.filterBy]);
|
}, [routerQueryParams.search, routerQueryParams.tags, routerQueryParams.filterBy]);
|
||||||
|
|
||||||
const selectedSecrets = useSelectedSecrets();
|
const selectedSecrets = useSelectedSecrets();
|
||||||
|
|||||||
Reference in New Issue
Block a user