mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-10 03:28:43 +00:00
pam: accounts table server-side filter, search, pagination
This commit is contained in:
@@ -12,3 +12,12 @@ export enum PamSessionStatus {
|
||||
Ended = "ended",
|
||||
Terminated = "terminated"
|
||||
}
|
||||
|
||||
export enum PamAccountOrderBy {
|
||||
Name = "name"
|
||||
}
|
||||
|
||||
export enum PamAccountView {
|
||||
Flat = "flat",
|
||||
Nested = "nested"
|
||||
}
|
||||
|
||||
@@ -82,7 +82,7 @@ export const useCreatePamAccount = () => {
|
||||
return data.account;
|
||||
},
|
||||
onSuccess: ({ projectId }) => {
|
||||
queryClient.invalidateQueries({ queryKey: pamKeys.listAccounts(projectId) });
|
||||
queryClient.invalidateQueries({ queryKey: pamKeys.listAccounts({ projectId }) });
|
||||
}
|
||||
});
|
||||
};
|
||||
@@ -99,7 +99,7 @@ export const useUpdatePamAccount = () => {
|
||||
return data.account;
|
||||
},
|
||||
onSuccess: ({ projectId }) => {
|
||||
queryClient.invalidateQueries({ queryKey: pamKeys.listAccounts(projectId) });
|
||||
queryClient.invalidateQueries({ queryKey: pamKeys.listAccounts({ projectId }) });
|
||||
}
|
||||
});
|
||||
};
|
||||
@@ -115,7 +115,7 @@ export const useDeletePamAccount = () => {
|
||||
return data.account;
|
||||
},
|
||||
onSuccess: ({ projectId }) => {
|
||||
queryClient.invalidateQueries({ queryKey: pamKeys.listAccounts(projectId) });
|
||||
queryClient.invalidateQueries({ queryKey: pamKeys.listAccounts({ projectId }) });
|
||||
}
|
||||
});
|
||||
};
|
||||
@@ -130,7 +130,7 @@ export const useCreatePamFolder = () => {
|
||||
return data.folder;
|
||||
},
|
||||
onSuccess: ({ projectId }) => {
|
||||
queryClient.invalidateQueries({ queryKey: pamKeys.listAccounts(projectId) });
|
||||
queryClient.invalidateQueries({ queryKey: pamKeys.listAccounts({ projectId }) });
|
||||
}
|
||||
});
|
||||
};
|
||||
@@ -147,7 +147,7 @@ export const useUpdatePamFolder = () => {
|
||||
return data.folder;
|
||||
},
|
||||
onSuccess: ({ projectId }) => {
|
||||
queryClient.invalidateQueries({ queryKey: pamKeys.listAccounts(projectId) });
|
||||
queryClient.invalidateQueries({ queryKey: pamKeys.listAccounts({ projectId }) });
|
||||
}
|
||||
});
|
||||
};
|
||||
@@ -163,7 +163,7 @@ export const useDeletePamFolder = () => {
|
||||
return data.folder;
|
||||
},
|
||||
onSuccess: ({ projectId }) => {
|
||||
queryClient.invalidateQueries({ queryKey: pamKeys.listAccounts(projectId) });
|
||||
queryClient.invalidateQueries({ queryKey: pamKeys.listAccounts({ projectId }) });
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
@@ -4,7 +4,7 @@ import { apiRequest } from "@app/config/request";
|
||||
|
||||
import { TPamResourceOption } from "./types/resource-options";
|
||||
import { PamResourceType } from "./enums";
|
||||
import { TPamAccount, TPamFolder, TPamResource, TPamSession } from "./types";
|
||||
import { TListPamAccountsDTO, TPamAccount, TPamFolder, TPamResource, TPamSession } from "./types";
|
||||
|
||||
export const pamKeys = {
|
||||
all: ["pam"] as const,
|
||||
@@ -19,7 +19,12 @@ export const pamKeys = {
|
||||
resourceType,
|
||||
resourceId
|
||||
],
|
||||
listAccounts: (projectId: string) => [...pamKeys.account(), "list", projectId],
|
||||
listAccounts: ({ projectId, ...params }: TListPamAccountsDTO) => [
|
||||
...pamKeys.account(),
|
||||
"list",
|
||||
projectId,
|
||||
params
|
||||
],
|
||||
getSession: (sessionId: string) => [...pamKeys.session(), "get", sessionId],
|
||||
listSessions: (projectId: string) => [...pamKeys.session(), "list", projectId]
|
||||
};
|
||||
@@ -98,25 +103,32 @@ export const useGetPamResourceById = (
|
||||
};
|
||||
|
||||
// Accounts
|
||||
type TListPamAccountsResponse = {
|
||||
accounts: TPamAccount[];
|
||||
folders: TPamFolder[];
|
||||
totalCount: number;
|
||||
folderId?: string;
|
||||
folderPaths: Record<string, string>;
|
||||
};
|
||||
|
||||
export const useListPamAccounts = (
|
||||
projectId: string,
|
||||
params: TListPamAccountsDTO,
|
||||
options?: Omit<
|
||||
UseQueryOptions<
|
||||
{ accounts: TPamAccount[]; folders: TPamFolder[] },
|
||||
TListPamAccountsResponse,
|
||||
unknown,
|
||||
{ accounts: TPamAccount[]; folders: TPamFolder[] },
|
||||
TListPamAccountsResponse,
|
||||
ReturnType<typeof pamKeys.listAccounts>
|
||||
>,
|
||||
"queryKey" | "queryFn"
|
||||
>
|
||||
) => {
|
||||
return useQuery({
|
||||
queryKey: pamKeys.listAccounts(projectId),
|
||||
queryKey: pamKeys.listAccounts(params),
|
||||
queryFn: async () => {
|
||||
const { data } = await apiRequest.get<{ accounts: TPamAccount[]; folders: TPamFolder[] }>(
|
||||
"/api/v1/pam/accounts",
|
||||
{ params: { projectId } }
|
||||
);
|
||||
const { data } = await apiRequest.get<TListPamAccountsResponse>("/api/v1/pam/accounts", {
|
||||
params
|
||||
});
|
||||
|
||||
return data;
|
||||
},
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { PamResourceType, PamSessionStatus } from "../enums";
|
||||
import { OrderByDirection } from "../../generic/types";
|
||||
import { PamAccountOrderBy, PamAccountView, PamResourceType, PamSessionStatus } from "../enums";
|
||||
import { TMySQLAccount, TMySQLResource } from "./mysql-resource";
|
||||
import { TPostgresAccount, TPostgresResource } from "./postgres-resource";
|
||||
|
||||
@@ -63,6 +64,17 @@ export type TDeletePamResourceDTO = {
|
||||
};
|
||||
|
||||
// Account DTOs
|
||||
export type TListPamAccountsDTO = {
|
||||
projectId: string;
|
||||
accountPath?: string | null;
|
||||
accountView?: PamAccountView;
|
||||
offset?: number;
|
||||
limit?: number;
|
||||
orderBy?: PamAccountOrderBy;
|
||||
orderDirection?: OrderByDirection;
|
||||
search?: string;
|
||||
};
|
||||
|
||||
export type TCreatePamAccountDTO = Pick<
|
||||
TPamAccount,
|
||||
"name" | "description" | "credentials" | "projectId" | "resourceId" | "folderId"
|
||||
|
||||
@@ -1,13 +1,9 @@
|
||||
import { Button } from "@app/components/v2";
|
||||
|
||||
export enum AccountView {
|
||||
Flat = "flat",
|
||||
Nested = "nested"
|
||||
}
|
||||
import { PamAccountView } from "@app/hooks/api/pam";
|
||||
|
||||
type Props = {
|
||||
value: AccountView;
|
||||
onChange: (value: AccountView) => void;
|
||||
value: PamAccountView;
|
||||
onChange: (value: PamAccountView) => void;
|
||||
};
|
||||
|
||||
export const AccountViewToggle = ({ value, onChange }: Props) => {
|
||||
@@ -16,11 +12,11 @@ export const AccountViewToggle = ({ value, onChange }: Props) => {
|
||||
<Button
|
||||
variant="outline_bg"
|
||||
onClick={() => {
|
||||
onChange(AccountView.Flat);
|
||||
onChange(PamAccountView.Flat);
|
||||
}}
|
||||
size="xs"
|
||||
className={`${
|
||||
value === AccountView.Flat ? "bg-mineshaft-500" : "bg-transparent"
|
||||
value === PamAccountView.Flat ? "bg-mineshaft-500" : "bg-transparent"
|
||||
} min-w-[2.4rem] rounded border-none hover:bg-mineshaft-600`}
|
||||
>
|
||||
Hide Folders
|
||||
@@ -28,11 +24,11 @@ export const AccountViewToggle = ({ value, onChange }: Props) => {
|
||||
<Button
|
||||
variant="outline_bg"
|
||||
onClick={() => {
|
||||
onChange(AccountView.Nested);
|
||||
onChange(PamAccountView.Nested);
|
||||
}}
|
||||
size="xs"
|
||||
className={`${
|
||||
value === AccountView.Nested ? "bg-mineshaft-500" : "bg-transparent"
|
||||
value === PamAccountView.Nested ? "bg-mineshaft-500" : "bg-transparent"
|
||||
} min-w-[2.4rem] rounded border-none hover:bg-mineshaft-600`}
|
||||
>
|
||||
Show Folders
|
||||
|
||||
@@ -1,23 +1,9 @@
|
||||
import { ContentLoader } from "@app/components/v2";
|
||||
import { useProject } from "@app/context";
|
||||
import { useListPamAccounts } from "@app/hooks/api/pam";
|
||||
|
||||
import { PamAccountsTable } from "./PamAccountsTable";
|
||||
|
||||
export const PamAccountsSection = () => {
|
||||
const { currentProject } = useProject();
|
||||
|
||||
const { data, isPending } = useListPamAccounts(currentProject.id, {
|
||||
refetchInterval: 30000
|
||||
});
|
||||
|
||||
if (isPending) return <ContentLoader />;
|
||||
|
||||
return (
|
||||
<PamAccountsTable
|
||||
projectId={currentProject.id}
|
||||
accounts={data?.accounts || []}
|
||||
folders={data?.folders || []}
|
||||
/>
|
||||
);
|
||||
return <PamAccountsTable projectId={currentProject.id} />;
|
||||
};
|
||||
|
||||
@@ -29,6 +29,7 @@ import {
|
||||
Pagination,
|
||||
Table,
|
||||
TableContainer,
|
||||
TableSkeleton,
|
||||
TBody,
|
||||
Th,
|
||||
THead,
|
||||
@@ -40,11 +41,23 @@ import {
|
||||
ProjectPermissionPamAccountActions,
|
||||
ProjectPermissionSub
|
||||
} from "@app/context/ProjectPermissionContext/types";
|
||||
import { usePagination, usePopUp, useResetPageHelper } from "@app/hooks";
|
||||
import {
|
||||
getUserTablePreference,
|
||||
PreferenceKey,
|
||||
setUserTablePreference
|
||||
} from "@app/helpers/userTablePreferences";
|
||||
import { usePagination, usePopUp } from "@app/hooks";
|
||||
import { OrderByDirection } from "@app/hooks/api/generic/types";
|
||||
import { PAM_RESOURCE_TYPE_MAP, TPamAccount, TPamFolder } from "@app/hooks/api/pam";
|
||||
import {
|
||||
PAM_RESOURCE_TYPE_MAP,
|
||||
PamAccountOrderBy,
|
||||
PamAccountView,
|
||||
TPamAccount,
|
||||
TPamFolder
|
||||
} from "@app/hooks/api/pam";
|
||||
import { useListPamAccounts } from "@app/hooks/api/pam/queries";
|
||||
|
||||
import { AccountView, AccountViewToggle } from "./AccountViewToggle";
|
||||
import { AccountViewToggle } from "./AccountViewToggle";
|
||||
import { FolderBreadCrumbs } from "./FolderBreadCrumbs";
|
||||
import { PamAccessAccountModal } from "./PamAccessAccountModal";
|
||||
import { PamAccountRow } from "./PamAccountRow";
|
||||
@@ -56,21 +69,15 @@ import { PamFolderRow } from "./PamFolderRow";
|
||||
import { PamUpdateAccountModal } from "./PamUpdateAccountModal";
|
||||
import { PamUpdateFolderModal } from "./PamUpdateFolderModal";
|
||||
|
||||
enum OrderBy {
|
||||
Name = "name"
|
||||
}
|
||||
|
||||
type Filters = {
|
||||
resource: string[];
|
||||
};
|
||||
|
||||
type Props = {
|
||||
accounts: TPamAccount[];
|
||||
folders: TPamFolder[];
|
||||
projectId: string;
|
||||
};
|
||||
|
||||
export const PamAccountsTable = ({ accounts, folders, projectId }: Props) => {
|
||||
export const PamAccountsTable = ({ projectId }: Props) => {
|
||||
const navigate = useNavigate({ from: ROUTE_PATHS.Pam.AccountsPage.path });
|
||||
|
||||
const { popUp, handlePopUpOpen, handlePopUpClose, handlePopUpToggle } = usePopUp([
|
||||
@@ -92,7 +99,9 @@ export const PamAccountsTable = ({ accounts, folders, projectId }: Props) => {
|
||||
from: ROUTE_PATHS.Pam.AccountsPage.id
|
||||
});
|
||||
|
||||
const [accountView, setAccountView] = useState<AccountView>(initAccountView ?? AccountView.Flat);
|
||||
const [accountView, setAccountView] = useState<PamAccountView>(
|
||||
initAccountView ?? PamAccountView.Flat
|
||||
);
|
||||
|
||||
const [filters, setFilters] = useState<Filters>({
|
||||
resource: []
|
||||
@@ -100,10 +109,11 @@ export const PamAccountsTable = ({ accounts, folders, projectId }: Props) => {
|
||||
|
||||
const {
|
||||
search,
|
||||
debouncedSearch,
|
||||
setSearch,
|
||||
setPage,
|
||||
page,
|
||||
perPage,
|
||||
setPage,
|
||||
setPerPage,
|
||||
offset,
|
||||
orderDirection,
|
||||
@@ -111,114 +121,67 @@ export const PamAccountsTable = ({ accounts, folders, projectId }: Props) => {
|
||||
orderBy,
|
||||
setOrderDirection,
|
||||
setOrderBy
|
||||
} = usePagination<OrderBy>(OrderBy.Name, { initPerPage: 20, initSearch });
|
||||
} = usePagination<PamAccountOrderBy>(PamAccountOrderBy.Name, {
|
||||
initPerPage: getUserTablePreference("pamAccountsTable", PreferenceKey.PerPage, 20),
|
||||
initSearch
|
||||
});
|
||||
|
||||
const { foldersByParentId, pathMap, folderPaths } = useMemo(() => {
|
||||
const foldersById: Record<string, TPamFolder> = {};
|
||||
const tempFoldersByParentId: Record<string, TPamFolder[]> = { null: [] };
|
||||
const tempPathMap: Record<string, string> = { "/": "null" };
|
||||
const tempFolderPaths: Record<string, string> = {};
|
||||
const handlePerPageChange = (newPerPage: number) => {
|
||||
setPerPage(newPerPage);
|
||||
setUserTablePreference("pamAccountsTable", PreferenceKey.PerPage, newPerPage);
|
||||
};
|
||||
|
||||
folders.forEach((folder) => {
|
||||
foldersById[folder.id] = folder;
|
||||
if (!tempFoldersByParentId[folder.parentId || "null"]) {
|
||||
tempFoldersByParentId[folder.parentId || "null"] = [];
|
||||
}
|
||||
tempFoldersByParentId[folder.parentId || "null"].push(folder);
|
||||
});
|
||||
const { data, isLoading } = useListPamAccounts({
|
||||
projectId,
|
||||
accountPath,
|
||||
accountView,
|
||||
offset,
|
||||
limit: perPage,
|
||||
search: debouncedSearch,
|
||||
orderBy,
|
||||
orderDirection
|
||||
});
|
||||
|
||||
const buildPaths = (parentId: string | null, currentPath: string) => {
|
||||
(tempFoldersByParentId[parentId || "null"] || []).forEach((folder) => {
|
||||
const newPath = `${currentPath}${folder.name}/`;
|
||||
tempPathMap[newPath] = folder.id;
|
||||
tempFolderPaths[folder.id] = newPath;
|
||||
buildPaths(folder.id, newPath);
|
||||
});
|
||||
};
|
||||
|
||||
buildPaths(null, "/");
|
||||
|
||||
return {
|
||||
foldersByParentId: tempFoldersByParentId,
|
||||
pathMap: tempPathMap,
|
||||
folderPaths: tempFolderPaths
|
||||
};
|
||||
}, [folders]);
|
||||
|
||||
const effectiveFolderIdForFiltering = useMemo(() => {
|
||||
if (accountView === AccountView.Flat) {
|
||||
return null;
|
||||
}
|
||||
const folderId = pathMap[accountPath];
|
||||
return folderId === "null" ? null : folderId || null;
|
||||
}, [accountView, accountPath, pathMap]);
|
||||
const accounts = data?.accounts || [];
|
||||
const folders = data?.folders || [];
|
||||
const totalCount = data?.totalCount || 0;
|
||||
const folderPaths = data?.folderPaths || {};
|
||||
const currentFolderId = data?.folderId ?? null;
|
||||
|
||||
const foldersToRender = useMemo(() => {
|
||||
if (accountView === AccountView.Flat) {
|
||||
if (accountView === PamAccountView.Flat) {
|
||||
return [];
|
||||
}
|
||||
return (foldersByParentId[effectiveFolderIdForFiltering || "null"] || []).filter((folder) =>
|
||||
return folders.filter((folder) =>
|
||||
folder.name.toLowerCase().includes(search.trim().toLowerCase())
|
||||
);
|
||||
}, [accountView, effectiveFolderIdForFiltering, foldersByParentId, search]);
|
||||
|
||||
const accountsToProcess = useMemo(() => {
|
||||
if (accountView === AccountView.Flat) {
|
||||
return accounts;
|
||||
}
|
||||
return accounts.filter(
|
||||
(acc) => (acc.folderId || "null") === (effectiveFolderIdForFiltering || "null")
|
||||
);
|
||||
}, [accountView, accounts, effectiveFolderIdForFiltering]);
|
||||
}, [accountView, folders, search]);
|
||||
|
||||
const filteredAccounts = useMemo(
|
||||
() =>
|
||||
accountsToProcess
|
||||
.filter((account) => {
|
||||
const {
|
||||
name,
|
||||
description,
|
||||
resource: { name: resourceName, id: resourceId }
|
||||
} = account;
|
||||
accounts.filter((account) => {
|
||||
const {
|
||||
name,
|
||||
description,
|
||||
resource: { name: resourceName, id: resourceId }
|
||||
} = account;
|
||||
|
||||
if (filters.resource.length && !filters.resource.includes(resourceId)) {
|
||||
return false;
|
||||
}
|
||||
if (filters.resource.length && !filters.resource.includes(resourceId)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const searchValue = search.trim().toLowerCase();
|
||||
const path = (account.folderId && folderPaths[account.folderId]) || "";
|
||||
const searchValue = search.trim().toLowerCase();
|
||||
|
||||
return (
|
||||
name.toLowerCase().includes(searchValue) ||
|
||||
resourceName.toLowerCase().includes(searchValue) ||
|
||||
(description || "").toLowerCase().includes(searchValue) ||
|
||||
path.toLowerCase().includes(searchValue)
|
||||
);
|
||||
})
|
||||
.sort((a, b) => {
|
||||
const [accOne, accTwo] = orderDirection === OrderByDirection.ASC ? [a, b] : [b, a];
|
||||
|
||||
switch (orderBy) {
|
||||
case OrderBy.Name:
|
||||
default:
|
||||
return accOne.name.toLowerCase().localeCompare(accTwo.name.toLowerCase());
|
||||
}
|
||||
}),
|
||||
[accountsToProcess, orderDirection, search, orderBy, filters, folderPaths]
|
||||
return (
|
||||
name.toLowerCase().includes(searchValue) ||
|
||||
resourceName.toLowerCase().includes(searchValue) ||
|
||||
(description || "").toLowerCase().includes(searchValue)
|
||||
);
|
||||
}),
|
||||
[accounts, search, filters]
|
||||
);
|
||||
|
||||
useResetPageHelper({
|
||||
totalCount: filteredAccounts.length,
|
||||
offset,
|
||||
setPage
|
||||
});
|
||||
|
||||
const currentPageData = useMemo(
|
||||
() => filteredAccounts.slice(offset, perPage * page),
|
||||
[filteredAccounts, offset, perPage, page]
|
||||
);
|
||||
|
||||
const handleSort = (column: OrderBy) => {
|
||||
const handleSort = (column: PamAccountOrderBy) => {
|
||||
if (column === orderBy) {
|
||||
toggleOrderDirection();
|
||||
return;
|
||||
@@ -228,15 +191,16 @@ export const PamAccountsTable = ({ accounts, folders, projectId }: Props) => {
|
||||
setOrderDirection(OrderByDirection.ASC);
|
||||
};
|
||||
|
||||
const getClassName = (col: OrderBy) => twMerge("ml-2", orderBy === col ? "" : "opacity-30");
|
||||
const getClassName = (col: PamAccountOrderBy) =>
|
||||
twMerge("ml-2", orderBy === col ? "" : "opacity-30");
|
||||
|
||||
const getColSortIcon = (col: OrderBy) =>
|
||||
const getColSortIcon = (col: PamAccountOrderBy) =>
|
||||
orderDirection === OrderByDirection.DESC && orderBy === col ? faArrowUp : faArrowDown;
|
||||
|
||||
const isTableFiltered = Boolean(filters.resource.length);
|
||||
|
||||
const handleFolderClick = (folder: TPamFolder) => {
|
||||
if (accountView === AccountView.Flat) {
|
||||
if (accountView === PamAccountView.Flat) {
|
||||
return;
|
||||
}
|
||||
const newPath = `${accountPath}${folder.name}/`;
|
||||
@@ -256,7 +220,7 @@ export const PamAccountsTable = ({ accounts, folders, projectId }: Props) => {
|
||||
|
||||
return (
|
||||
<div>
|
||||
{accountView === AccountView.Nested && <FolderBreadCrumbs path={accountPath} />}
|
||||
{accountView === PamAccountView.Nested && <FolderBreadCrumbs path={accountPath} />}
|
||||
<div className="mt-4 flex gap-2">
|
||||
<ProjectPermissionCan I={ProjectPermissionActions.Read} a={ProjectPermissionSub.PamFolders}>
|
||||
{(isAllowed) =>
|
||||
@@ -268,8 +232,8 @@ export const PamAccountsTable = ({ accounts, folders, projectId }: Props) => {
|
||||
navigate({
|
||||
search: (prev) => ({
|
||||
...prev,
|
||||
accountView: e === AccountView.Flat ? undefined : e,
|
||||
accountPath: e === AccountView.Flat ? "/" : prev.accountPath
|
||||
accountView: e === PamAccountView.Flat ? undefined : e,
|
||||
accountPath: e === PamAccountView.Flat ? "/" : prev.accountPath
|
||||
})
|
||||
});
|
||||
}}
|
||||
@@ -358,12 +322,12 @@ export const PamAccountsTable = ({ accounts, folders, projectId }: Props) => {
|
||||
leftIcon={<FontAwesomeIcon icon={faPlus} />}
|
||||
onClick={() => handlePopUpOpen("addAccount")}
|
||||
isDisabled={!isAllowedToCreateAccounts}
|
||||
className={`h-10 transition-colors ${accountView === AccountView.Flat ? "" : "rounded-r-none"}`}
|
||||
className={`h-10 transition-colors ${accountView === PamAccountView.Flat ? "" : "rounded-r-none"}`}
|
||||
>
|
||||
Add Account
|
||||
</Button>
|
||||
|
||||
{accountView !== AccountView.Flat && (
|
||||
{accountView !== PamAccountView.Flat && (
|
||||
<DropdownMenu
|
||||
open={popUp.misc.isOpen}
|
||||
onOpenChange={(isOpen) => handlePopUpToggle("misc", isOpen)}
|
||||
@@ -414,11 +378,11 @@ export const PamAccountsTable = ({ accounts, folders, projectId }: Props) => {
|
||||
Accounts
|
||||
<IconButton
|
||||
variant="plain"
|
||||
className={getClassName(OrderBy.Name)}
|
||||
className={getClassName(PamAccountOrderBy.Name)}
|
||||
ariaLabel="sort"
|
||||
onClick={() => handleSort(OrderBy.Name)}
|
||||
onClick={() => handleSort(PamAccountOrderBy.Name)}
|
||||
>
|
||||
<FontAwesomeIcon icon={getColSortIcon(OrderBy.Name)} />
|
||||
<FontAwesomeIcon icon={getColSortIcon(PamAccountOrderBy.Name)} />
|
||||
</IconButton>
|
||||
</div>
|
||||
</Th>
|
||||
@@ -426,45 +390,48 @@ export const PamAccountsTable = ({ accounts, folders, projectId }: Props) => {
|
||||
</Tr>
|
||||
</THead>
|
||||
<TBody>
|
||||
{accountView !== AccountView.Flat &&
|
||||
foldersToRender.map((folder) => (
|
||||
<PamFolderRow
|
||||
key={folder.id}
|
||||
folder={folder}
|
||||
search={search}
|
||||
onClick={() => handleFolderClick(folder)}
|
||||
onUpdate={(e) => handlePopUpOpen("updateFolder", e)}
|
||||
onDelete={(e) => handlePopUpOpen("deleteFolder", e)}
|
||||
/>
|
||||
))}
|
||||
{currentPageData.map((account) => (
|
||||
<PamAccountRow
|
||||
key={account.id}
|
||||
account={account}
|
||||
search={search}
|
||||
isFlatView={accountView === AccountView.Flat}
|
||||
accountPath={
|
||||
account.folderId ? folderPaths[account.folderId]?.slice(0, -1) : undefined
|
||||
}
|
||||
onAccess={(e) => {
|
||||
handlePopUpOpen("accessAccount", e);
|
||||
}}
|
||||
onUpdate={(e) => handlePopUpOpen("updateAccount", e)}
|
||||
onDelete={(e) => handlePopUpOpen("deleteAccount", e)}
|
||||
/>
|
||||
))}
|
||||
{isLoading && <TableSkeleton columns={2} innerKey="pam-accounts" />}
|
||||
{!isLoading && (
|
||||
<>
|
||||
{accountView !== PamAccountView.Flat &&
|
||||
foldersToRender.map((folder) => (
|
||||
<PamFolderRow
|
||||
key={folder.id}
|
||||
folder={folder}
|
||||
search={search}
|
||||
onClick={() => handleFolderClick(folder)}
|
||||
onUpdate={(e) => handlePopUpOpen("updateFolder", e)}
|
||||
onDelete={(e) => handlePopUpOpen("deleteFolder", e)}
|
||||
/>
|
||||
))}
|
||||
{filteredAccounts.map((account) => (
|
||||
<PamAccountRow
|
||||
key={account.id}
|
||||
account={account}
|
||||
search={search}
|
||||
isFlatView={accountView === PamAccountView.Flat}
|
||||
accountPath={account.folderId ? folderPaths[account.folderId] : undefined}
|
||||
onAccess={(e) => {
|
||||
handlePopUpOpen("accessAccount", e);
|
||||
}}
|
||||
onUpdate={(e) => handlePopUpOpen("updateAccount", e)}
|
||||
onDelete={(e) => handlePopUpOpen("deleteAccount", e)}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
</TBody>
|
||||
</Table>
|
||||
{Boolean(filteredAccounts.length) && (
|
||||
{Boolean(totalCount) && !isLoading && (
|
||||
<Pagination
|
||||
count={filteredAccounts.length}
|
||||
count={totalCount}
|
||||
page={page}
|
||||
perPage={perPage}
|
||||
onChangePage={setPage}
|
||||
onChangePerPage={setPerPage}
|
||||
onChangePage={(newPage) => setPage(newPage)}
|
||||
onChangePerPage={handlePerPageChange}
|
||||
/>
|
||||
)}
|
||||
{isContentEmpty && (
|
||||
{!isLoading && isContentEmpty && (
|
||||
<EmptyState
|
||||
title={isSearchEmpty ? "No accounts match search" : "No accounts"}
|
||||
icon={isSearchEmpty ? faSearch : faCircleXmark}
|
||||
@@ -485,7 +452,7 @@ export const PamAccountsTable = ({ accounts, folders, projectId }: Props) => {
|
||||
isOpen={popUp.addFolder.isOpen}
|
||||
onOpenChange={(isOpen) => handlePopUpToggle("addFolder", isOpen)}
|
||||
projectId={projectId}
|
||||
currentFolderId={effectiveFolderIdForFiltering}
|
||||
currentFolderId={currentFolderId}
|
||||
/>
|
||||
<PamAccessAccountModal
|
||||
isOpen={popUp.accessAccount.isOpen}
|
||||
@@ -506,7 +473,7 @@ export const PamAccountsTable = ({ accounts, folders, projectId }: Props) => {
|
||||
isOpen={popUp.addAccount.isOpen}
|
||||
onOpenChange={(isOpen) => handlePopUpToggle("addAccount", isOpen)}
|
||||
projectId={projectId}
|
||||
currentFolderId={effectiveFolderIdForFiltering}
|
||||
currentFolderId={currentFolderId}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -2,12 +2,13 @@ import { createFileRoute, linkOptions, stripSearchParams } from "@tanstack/react
|
||||
import { zodValidator } from "@tanstack/zod-adapter";
|
||||
import { z } from "zod";
|
||||
|
||||
import { AccountView } from "./components/AccountViewToggle";
|
||||
import { PamAccountView } from "@app/hooks/api/pam";
|
||||
|
||||
import { PamAccountsPage } from "./PamAccountsPage";
|
||||
|
||||
const PamAccountsPageQueryParamsSchema = z.object({
|
||||
search: z.string().optional(),
|
||||
accountView: z.nativeEnum(AccountView).optional(),
|
||||
accountView: z.nativeEnum(PamAccountView).optional(),
|
||||
accountPath: z.string().catch("/")
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user