From d57ff99788c2ff0f11c87233969fdc063cb0f2ce Mon Sep 17 00:00:00 2001 From: Celal Zamanoglu <95054566+celalzamanoglu@users.noreply.github.com> Date: Tue, 23 Dec 2025 00:02:16 +0300 Subject: [PATCH] fix: old credentials are not accessible (#4358) --- .../routes/credentials/CredentialsList.tsx | 65 ++++++++++++++++--- .../routes/credentials/CredentialsModal.tsx | 4 +- .../CredentialParameterSourceSelector.tsx | 4 +- .../components/CredentialSelector.tsx | 4 +- .../LoginBlockCredentialSelector.tsx | 1 + .../workflows/hooks/useCredentialsQuery.ts | 13 ++-- 6 files changed, 76 insertions(+), 15 deletions(-) diff --git a/skyvern-frontend/src/routes/credentials/CredentialsList.tsx b/skyvern-frontend/src/routes/credentials/CredentialsList.tsx index 2dd5fb68..51722086 100644 --- a/skyvern-frontend/src/routes/credentials/CredentialsList.tsx +++ b/skyvern-frontend/src/routes/credentials/CredentialsList.tsx @@ -1,6 +1,16 @@ import { Skeleton } from "@/components/ui/skeleton"; import { CredentialItem } from "./CredentialItem"; import { useCredentialsQuery } from "@/routes/workflows/hooks/useCredentialsQuery"; +import { + Pagination, + PaginationContent, + PaginationItem, + PaginationLink, + PaginationNext, + PaginationPrevious, +} from "@/components/ui/pagination"; +import { cn } from "@/util/utils"; +import { useState } from "react"; type CredentialFilter = "password" | "credit_card" | "secret"; @@ -14,8 +24,14 @@ const EMPTY_MESSAGE: Record = { secret: "No secrets stored yet.", }; +const PAGE_SIZE = 25; + function CredentialsList({ filter }: Props = {}) { - const { data: credentials, isLoading } = useCredentialsQuery(); + const [page, setPage] = useState(1); + const { data: credentials, isLoading } = useCredentialsQuery({ + page, + page_size: PAGE_SIZE, + }); if (isLoading) { return ( @@ -42,7 +58,7 @@ function CredentialsList({ filter }: Props = {}) { ); })(); - if (filteredCredentials.length === 0) { + if (filteredCredentials.length === 0 && page === 1) { return (
{filter ? EMPTY_MESSAGE[filter] : "No credentials stored yet."} @@ -50,14 +66,47 @@ function CredentialsList({ filter }: Props = {}) { ); } + const hasNextPage = credentials.length === PAGE_SIZE; + return (
- {filteredCredentials.map((credential) => ( - - ))} +
+ {filteredCredentials.map((credential) => ( + + ))} +
+ {(page > 1 || hasNextPage) && ( + + + + { + if (page > 1) { + setPage((prev) => Math.max(1, prev - 1)); + } + }} + /> + + + {page} + + + { + if (hasNextPage) { + setPage((prev) => prev + 1); + } + }} + /> + + + + )}
); } diff --git a/skyvern-frontend/src/routes/credentials/CredentialsModal.tsx b/skyvern-frontend/src/routes/credentials/CredentialsModal.tsx index 7dea5ae5..ad9d09dd 100644 --- a/skyvern-frontend/src/routes/credentials/CredentialsModal.tsx +++ b/skyvern-frontend/src/routes/credentials/CredentialsModal.tsx @@ -73,7 +73,9 @@ function CredentialsModal({ onCredentialCreated }: Props) { const credentialGetter = useCredentialGetter(); const queryClient = useQueryClient(); const { isOpen, type, setIsOpen } = useCredentialModalState(); - const { data: credentials } = useCredentialsQuery(); + const { data: credentials } = useCredentialsQuery({ + page_size: 100, + }); const [passwordCredentialValues, setPasswordCredentialValues] = useState( PASSWORD_CREDENTIAL_INITIAL_VALUES, ); diff --git a/skyvern-frontend/src/routes/workflows/components/CredentialParameterSourceSelector.tsx b/skyvern-frontend/src/routes/workflows/components/CredentialParameterSourceSelector.tsx index 82a3cf8c..a14892ce 100644 --- a/skyvern-frontend/src/routes/workflows/components/CredentialParameterSourceSelector.tsx +++ b/skyvern-frontend/src/routes/workflows/components/CredentialParameterSourceSelector.tsx @@ -22,7 +22,9 @@ type Props = { }; function CredentialParameterSourceSelector({ value, onChange }: Props) { - const { data: credentials, isFetching } = useCredentialsQuery(); + const { data: credentials, isFetching } = useCredentialsQuery({ + page_size: 100, // Reasonable limit for dropdown selector + }); const { setIsOpen, setType } = useCredentialModalState(); const { parameters: workflowParameters } = useWorkflowParametersStore(); const workflowParametersOfTypeCredentialId = workflowParameters.filter( diff --git a/skyvern-frontend/src/routes/workflows/components/CredentialSelector.tsx b/skyvern-frontend/src/routes/workflows/components/CredentialSelector.tsx index d3516662..8c9acd39 100644 --- a/skyvern-frontend/src/routes/workflows/components/CredentialSelector.tsx +++ b/skyvern-frontend/src/routes/workflows/components/CredentialSelector.tsx @@ -23,7 +23,9 @@ type Props = { function CredentialSelector({ value, onChange }: Props) { const { setIsOpen, setType } = useCredentialModalState(); - const { data: credentials, isFetching } = useCredentialsQuery(); + const { data: credentials, isFetching } = useCredentialsQuery({ + page_size: 100, // Reasonable limit for dropdown selector + }); if (isFetching) { return ; diff --git a/skyvern-frontend/src/routes/workflows/editor/nodes/LoginNode/LoginBlockCredentialSelector.tsx b/skyvern-frontend/src/routes/workflows/editor/nodes/LoginNode/LoginBlockCredentialSelector.tsx index 30c8b322..855501a6 100644 --- a/skyvern-frontend/src/routes/workflows/editor/nodes/LoginNode/LoginBlockCredentialSelector.tsx +++ b/skyvern-frontend/src/routes/workflows/editor/nodes/LoginNode/LoginBlockCredentialSelector.tsx @@ -65,6 +65,7 @@ function LoginBlockCredentialSelector({ nodeId, value, onChange }: Props) { const isCloud = useContext(CloudContext); const { data: credentials = [], isFetching } = useCredentialsQuery({ enabled: isCloud, + page_size: 100, }); if (isCloud && isFetching) { diff --git a/skyvern-frontend/src/routes/workflows/hooks/useCredentialsQuery.ts b/skyvern-frontend/src/routes/workflows/hooks/useCredentialsQuery.ts index 38ee3419..1749b927 100644 --- a/skyvern-frontend/src/routes/workflows/hooks/useCredentialsQuery.ts +++ b/skyvern-frontend/src/routes/workflows/hooks/useCredentialsQuery.ts @@ -9,20 +9,25 @@ type UseQueryOptions = Omit< "queryKey" | "queryFn" >; -type Props = UseQueryOptions; +type Props = UseQueryOptions & { + page?: number; + page_size?: number; +}; function useCredentialsQuery(props: Props = {}) { + const { page = 1, page_size = 25, ...queryOptions } = props; const credentialGetter = useCredentialGetter(); return useQuery>({ - queryKey: ["credentials"], + queryKey: ["credentials", page, page_size], queryFn: async () => { const client = await getClient(credentialGetter); const params = new URLSearchParams(); - params.set("page_size", "25"); + params.set("page", String(page)); + params.set("page_size", String(page_size)); return client.get("/credentials", { params }).then((res) => res.data); }, - ...props, + ...queryOptions, }); }