diff --git a/.gitignore b/.gitignore index 45b1552b..53e04a08 100644 --- a/.gitignore +++ b/.gitignore @@ -23,4 +23,7 @@ tmp/* .local/ # Coverage files -coverage.* \ No newline at end of file +coverage.* + +# Debug files +__debug_bin* \ No newline at end of file diff --git a/internal/persistence/local/dag_store.go b/internal/persistence/local/dag_store.go index 3831ec3a..29a9df96 100644 --- a/internal/persistence/local/dag_store.go +++ b/internal/persistence/local/dag_store.go @@ -166,10 +166,9 @@ func (d *dagStoreImpl) searchName(fileName string, searchText *string) bool { if searchText == nil { return true } - fileName = strings.TrimSuffix(fileName, path.Ext(fileName)) - - return strings.Contains(fileName, *searchText) + ret := strings.Contains(strings.ToLower(fileName), strings.ToLower(*searchText)) + return ret } func (d *dagStoreImpl) searchTags(tags []string, searchTag *string) bool { diff --git a/ui/package.json b/ui/package.json index 08812cbe..b94ed47d 100644 --- a/ui/package.json +++ b/ui/package.json @@ -75,7 +75,7 @@ "monaco-react": "^1.1.0", "monaco-yaml": "^4.0.4", "prism": "^4.1.2", - "react": "^18.1.0", + "react": "^18.3.0", "react-cookie": "^4.1.1", "react-dom": "^18.1.0", "react-monaco-editor": "^0.54.0", diff --git a/ui/src/App.tsx b/ui/src/App.tsx index 977def8e..56084e69 100644 --- a/ui/src/App.tsx +++ b/ui/src/App.tsx @@ -8,6 +8,7 @@ import { AppBarContext } from './contexts/AppBarContext'; import { SWRConfig } from 'swr'; import fetchJson from './lib/fetchJson'; import Search from './pages/search'; +import { UserPreferencesProvider } from './contexts/UserPreference'; export type Config = { apiURL: string; @@ -38,18 +39,20 @@ function App({ config }: Props) { setTitle, }} > - - - - } /> - } /> - } /> - } /> - } /> - } /> - - - + + + + + } /> + } /> + } /> + } /> + } /> + } /> + + + + ); diff --git a/ui/src/components/molecules/DAGPagination.tsx b/ui/src/components/molecules/DAGPagination.tsx index 0745a843..5e272ef2 100644 --- a/ui/src/components/molecules/DAGPagination.tsx +++ b/ui/src/components/molecules/DAGPagination.tsx @@ -1,34 +1,83 @@ -import { Box, Pagination } from "@mui/material"; -import React from "react"; +import { Box, Pagination, TextField } from '@mui/material'; +import React from 'react'; type DAGPaginationProps = { - totalPages: number; - page: number; - pageChange: (page: number) => void; + totalPages: number; + page: number; + pageLimit: number; + pageChange: (page: number) => void; + onPageLimitChange: (pageLimit: number) => void; }; -const DAGPagination = ({ totalPages, page, pageChange }: DAGPaginationProps) => { - const handleChange = (event: React.ChangeEvent, value: number) => { - pageChange(value); - }; - - return ( - - - - ); -} - -export default DAGPagination; \ No newline at end of file +const DAGPagination = ({ + totalPages, + page, + pageChange, + pageLimit, + onPageLimitChange, +}: DAGPaginationProps) => { + const [inputValue, setInputValue] = React.useState(pageLimit.toString()); + + React.useEffect(() => { + setInputValue(pageLimit.toString()); + }, [pageLimit]); + + const handleChange = (event: React.ChangeEvent, value: number) => { + pageChange(value); + }; + + const handleLimitChange = (event: React.ChangeEvent) => { + const value = event.target.value; + setInputValue(value); + }; + + const commitChange = () => { + const numValue = parseInt(inputValue); + if (!isNaN(numValue) && numValue > 0) { + onPageLimitChange(numValue); + } else { + setInputValue(pageLimit.toString()); + } + }; + + const handleKeyDown = (event: React.KeyboardEvent) => { + if (event.key === 'Enter') { + commitChange(); + event.preventDefault(); + (event.target as HTMLInputElement).blur(); // Remove focus after Enter + } + }; + + return ( + + + + + ); +}; + +export default DAGPagination; diff --git a/ui/src/contexts/UserPreference.tsx b/ui/src/contexts/UserPreference.tsx new file mode 100644 index 00000000..9179333d --- /dev/null +++ b/ui/src/contexts/UserPreference.tsx @@ -0,0 +1,47 @@ +import React, { createContext, useCallback, useContext, useState } from 'react'; + +export type UserPreferences = { + pageLimit: number; +} + +const UserPreferencesContext = createContext<{ + preferences: UserPreferences; + updatePreference: ( + key: K, + value: UserPreferences[K] + ) => void; +}>(null!); + + +export function UserPreferencesProvider({ children }: { children: React.ReactNode }) { + const [preferences, setPreferences] = useState(() => { + try { + const saved = localStorage.getItem('user_preferences'); + return saved ? JSON.parse(saved) : { pageLimit: 50, theme: 'light' }; + } catch { + return { pageLimit: 50, theme: 'light' }; + } + }); + + const updatePreference = useCallback(( + key: K, + value: UserPreferences[K] + ) => { + setPreferences(prev => { + const next = { ...prev, [key]: value }; + localStorage.setItem('user_preferences', JSON.stringify(next)); + return next; + }); + }, []); + + return ( + + {children} + + ); + +} + +export function useUserPreferences() { + return useContext(UserPreferencesContext); +} \ No newline at end of file diff --git a/ui/src/pages/dags/index.tsx b/ui/src/pages/dags/index.tsx index 5a0c0d1a..66c04597 100644 --- a/ui/src/pages/dags/index.tsx +++ b/ui/src/pages/dags/index.tsx @@ -12,6 +12,7 @@ import { AppBarContext } from '../../contexts/AppBarContext'; import useSWR, { useSWRConfig } from 'swr'; import DAGPagination from '../../components/molecules/DAGPagination'; import { debounce } from 'lodash'; +import { useUserPreferences } from '../../contexts/UserPreference'; function DAGs() { const useQuery = () => new URLSearchParams(useLocation().search); @@ -21,18 +22,26 @@ function DAGs() { const [searchText, setSearchText] = React.useState(query.get('search') || ''); const [searchTag, setSearchTag] = React.useState(query.get('tag') || ''); const [page, setPage] = React.useState(parseInt(query.get('page') || '1')); - const [apiSearchText, setAPISearchText] = React.useState(query.get('search') || ''); - const [apiSearchTag, setAPISearchTag] = React.useState(query.get('tag') || ''); + const [apiSearchText, setAPISearchText] = React.useState( + query.get('search') || '' + ); + const [apiSearchTag, setAPISearchTag] = React.useState( + query.get('tag') || '' + ); + + const { preferences, updatePreference } = useUserPreferences(); + // Use preferences.pageLimit instead of local state + const handlePageLimitChange = (newLimit: number) => { + updatePreference('pageLimit', newLimit); + }; const { cache, mutate } = useSWRConfig(); - const endPoint =`/dags?${new URLSearchParams( - { - page: page.toString(), - limit: '50', - searchName: apiSearchText, - searchTag: apiSearchTag, - } - ).toString()}` + const endPoint = `/dags?${new URLSearchParams({ + page: page.toString(), + limit: preferences.pageLimit.toString(), + searchName: apiSearchText, + searchTag: apiSearchTag, + }).toString()}`; const { data } = useSWR(endPoint, null, { refreshInterval: 10000, revalidateIfStale: false, @@ -41,8 +50,12 @@ function DAGs() { const addSearchParam = (key: string, value: string) => { const locationQuery = new URLSearchParams(window.location.search); locationQuery.set(key, value); - window.history.pushState({}, '', `${window.location.pathname}?${locationQuery.toString()}`); - } + window.history.pushState( + {}, + '', + `${window.location.pathname}?${locationQuery.toString()}` + ); + }; const refreshFn = React.useCallback(() => { setTimeout(() => mutate(endPoint), 500); @@ -73,27 +86,35 @@ function DAGs() { setPage(page); }; - const debouncedAPISearchText = React.useMemo(() => debounce((searchText: string) => { - setAPISearchText(searchText); - }, 500), []); + const debouncedAPISearchText = React.useMemo( + () => + debounce((searchText: string) => { + setAPISearchText(searchText); + }, 500), + [] + ); - const debouncedAPISearchTag = React.useMemo(() => debounce((searchTag: string) => { - setAPISearchTag(searchTag); - }, 500), []); + const debouncedAPISearchTag = React.useMemo( + () => + debounce((searchTag: string) => { + setAPISearchTag(searchTag); + }, 500), + [] + ); const searchTextChange = (searchText: string) => { addSearchParam('search', searchText); setSearchText(searchText); setPage(1); debouncedAPISearchText(searchText); - } + }; const searchTagChange = (searchTag: string) => { addSearchParam('tag', searchTag); setSearchTag(searchTag); setPage(1); debouncedAPISearchTag(searchTag); - } + }; return ( - + )} diff --git a/ui/yarn.lock b/ui/yarn.lock index cd1d2f46..5d3a42d8 100644 --- a/ui/yarn.lock +++ b/ui/yarn.lock @@ -4103,10 +4103,10 @@ react@^16.3.2: object-assign "^4.1.1" prop-types "^15.6.2" -react@^18.1.0: - version "18.2.0" - resolved "https://registry.yarnpkg.com/react/-/react-18.2.0.tgz#555bd98592883255fa00de14f1151a917b5d77d5" - integrity sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ== +react@^18.3.0: + version "18.3.1" + resolved "https://registry.yarnpkg.com/react/-/react-18.3.1.tgz#49ab892009c53933625bd16b2533fc754cab2891" + integrity sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ== dependencies: loose-envify "^1.1.0"