@@ -5,6 +5,7 @@ import Modal from '@app/components/Common/Modal';
55import PageTitle from '@app/components/Common/PageTitle' ;
66import Table from '@app/components/Common/Table' ;
77import Tooltip from '@app/components/Common/Tooltip' ;
8+ import useDebouncedState from '@app/hooks/useDebouncedState' ;
89import { useUpdateQueryParams } from '@app/hooks/useUpdateQueryParams' ;
910import globalMessages from '@app/i18n/globalMessages' ;
1011import Error from '@app/pages/_error' ;
@@ -17,6 +18,7 @@ import {
1718 FilterIcon ,
1819 PauseIcon ,
1920 PlayIcon ,
21+ SearchIcon ,
2022} from '@heroicons/react/solid' ;
2123import type {
2224 LogMessage ,
@@ -59,6 +61,8 @@ const SettingsLogs = () => {
5961 const { addToast } = useToasts ( ) ;
6062 const [ currentFilter , setCurrentFilter ] = useState < Filter > ( 'debug' ) ;
6163 const [ currentPageSize , setCurrentPageSize ] = useState ( 25 ) ;
64+ const [ searchFilter , debouncedSearchFilter , setSearchFilter ] =
65+ useDebouncedState ( '' ) ;
6266 const [ refreshInterval , setRefreshInterval ] = useState ( 5000 ) ;
6367 const [ activeLog , setActiveLog ] = useState < {
6468 isOpen : boolean ;
@@ -76,7 +80,9 @@ const SettingsLogs = () => {
7680 const { data, error } = useSWR < LogsResultsResponse > (
7781 `/api/v1/settings/logs?take=${ currentPageSize } &skip=${
7882 pageIndex * currentPageSize
79- } &filter=${ currentFilter } `,
83+ } &filter=${ currentFilter } ${
84+ debouncedSearchFilter ? `&search=${ debouncedSearchFilter } ` : ''
85+ } `,
8086 {
8187 refreshInterval : refreshInterval ,
8288 revalidateOnFocus : false ,
@@ -118,15 +124,13 @@ const SettingsLogs = () => {
118124 } ) ;
119125 } ;
120126
121- if ( ! data && ! error ) {
122- return < LoadingSpinner /> ;
123- }
124-
125- if ( ! data ) {
127+ // check if there's no data and no errors in the table
128+ // so as to show a spinner inside the table and not refresh the whole component
129+ if ( ! data && error ) {
126130 return < Error statusCode = { 500 } /> ;
127131 }
128132
129- const hasNextPage = data . pageInfo . pages > pageIndex + 1 ;
133+ const hasNextPage = data ? .pageInfo . pages ?? 0 > pageIndex + 1 ;
130134 const hasPrevPage = pageIndex > 0 ;
131135
132136 return (
@@ -245,10 +249,21 @@ const SettingsLogs = () => {
245249 appDataPath : appData ? appData . appDataPath : '/app/config' ,
246250 } ) }
247251 </ p >
248- < div className = "mt-2 flex flex-grow flex-row sm:flex-grow-0 sm:justify-end" >
252+ < div className = "mt-2 flex flex-grow flex-col sm:flex-grow-0 sm:flex-row sm:justify-end" >
253+ < div className = "mb-2 flex flex-grow sm:mb-0 sm:mr-2 md:flex-grow-0" >
254+ < span className = "inline-flex cursor-default items-center rounded-l-md border border-r-0 border-gray-500 bg-gray-800 px-3 text-sm text-gray-100" >
255+ < SearchIcon className = "h-6 w-6" />
256+ </ span >
257+ < input
258+ type = "text"
259+ className = "rounded-r-only"
260+ value = { searchFilter }
261+ onChange = { ( e ) => setSearchFilter ( e . target . value as string ) }
262+ />
263+ </ div >
249264 < div className = "mb-2 flex flex-1 flex-row justify-between sm:mb-0 sm:flex-none" >
250265 < Button
251- className = "mr-2 w-full flex-grow"
266+ className = "mr-2 flex flex-grow"
252267 buttonType = { refreshInterval ? 'default' : 'primary' }
253268 onClick = { ( ) => toggleLogs ( ) }
254269 >
@@ -259,34 +274,34 @@ const SettingsLogs = () => {
259274 ) }
260275 </ span >
261276 </ Button >
262- </ div >
263- < div className = "mb-2 flex flex-1 sm:mb-0 sm:flex-none " >
264- < span className = "inline-flex cursor-default items-center rounded-l-md border border-r-0 border-gray-500 bg-gray-800 px-3 text-sm text-gray-100" >
265- < FilterIcon className = "h-6 w-6" / >
266- </ span >
267- < select
268- id = "filter"
269- name = "filter"
270- onChange = { ( e ) => {
271- setCurrentFilter ( e . target . value as Filter ) ;
272- router . push ( router . pathname ) ;
273- } }
274- value = { currentFilter }
275- className = "rounded-r-only"
276- >
277- < option value = "debug" >
278- { intl . formatMessage ( messages . filterDebug ) }
279- </ option >
280- < option value = "info" >
281- { intl . formatMessage ( messages . filterInfo ) }
282- </ option >
283- < option value = "warn" >
284- { intl . formatMessage ( messages . filterWarn ) }
285- </ option >
286- < option value = "error" >
287- { intl . formatMessage ( messages . filterError ) }
288- </ option >
289- </ select >
277+ < div className = "flex flex-grow" >
278+ < span className = "inline- flex cursor-default items-center rounded-l-md border border-r-0 border-gray-500 bg-gray-800 px-3 text-sm text-gray-100 " >
279+ < FilterIcon className = "h-6 w-6" / >
280+ </ span >
281+ < select
282+ id = "filter"
283+ name = "filter"
284+ onChange = { ( e ) => {
285+ setCurrentFilter ( e . target . value as Filter ) ;
286+ router . push ( router . pathname ) ;
287+ } }
288+ value = { currentFilter }
289+ className = "rounded-r-only"
290+ >
291+ < option value = "debug" >
292+ { intl . formatMessage ( messages . filterDebug ) }
293+ </ option >
294+ < option value = "info" >
295+ { intl . formatMessage ( messages . filterInfo ) }
296+ </ option >
297+ < option value = "warn" >
298+ { intl . formatMessage ( messages . filterWarn ) }
299+ </ option >
300+ < option value = "error" >
301+ { intl . formatMessage ( messages . filterError ) }
302+ </ option >
303+ </ select >
304+ </ div >
290305 </ div >
291306 </ div >
292307 < Table >
@@ -300,73 +315,81 @@ const SettingsLogs = () => {
300315 </ tr >
301316 </ thead >
302317 < Table . TBody >
303- { data . results . map ( ( row : LogMessage , index : number ) => {
304- return (
305- < tr key = { `log-list-${ index } ` } >
306- < Table . TD className = "text-gray-300" >
307- { intl . formatDate ( row . timestamp , {
308- year : 'numeric' ,
309- month : 'short' ,
310- day : '2-digit' ,
311- hour : 'numeric' ,
312- minute : 'numeric' ,
313- second : 'numeric' ,
314- } ) }
315- </ Table . TD >
316- < Table . TD className = "text-gray-300" >
317- < Badge
318- badgeType = {
319- row . level === 'error'
320- ? 'danger'
321- : row . level === 'warn'
322- ? 'warning'
323- : row . level === 'info'
324- ? 'success'
325- : 'default'
326- }
327- >
328- { row . level . toUpperCase ( ) }
329- </ Badge >
330- </ Table . TD >
331- < Table . TD className = "text-gray-300" >
332- { row . label ?? '' }
333- </ Table . TD >
334- < Table . TD className = "text-gray-300" > { row . message } </ Table . TD >
335- < Table . TD className = "-m-1 flex flex-wrap items-center justify-end" >
336- { row . data && (
318+ { ! data ? (
319+ < tr >
320+ < Table . TD colSpan = { 5 } noPadding >
321+ < LoadingSpinner />
322+ </ Table . TD >
323+ </ tr >
324+ ) : (
325+ data . results . map ( ( row : LogMessage , index : number ) => {
326+ return (
327+ < tr key = { `log-list-${ index } ` } >
328+ < Table . TD className = "text-gray-300" >
329+ { intl . formatDate ( row . timestamp , {
330+ year : 'numeric' ,
331+ month : 'short' ,
332+ day : '2-digit' ,
333+ hour : 'numeric' ,
334+ minute : 'numeric' ,
335+ second : 'numeric' ,
336+ } ) }
337+ </ Table . TD >
338+ < Table . TD className = "text-gray-300" >
339+ < Badge
340+ badgeType = {
341+ row . level === 'error'
342+ ? 'danger'
343+ : row . level === 'warn'
344+ ? 'warning'
345+ : row . level === 'info'
346+ ? 'success'
347+ : 'default'
348+ }
349+ >
350+ { row . level . toUpperCase ( ) }
351+ </ Badge >
352+ </ Table . TD >
353+ < Table . TD className = "text-gray-300" >
354+ { row . label ?? '' }
355+ </ Table . TD >
356+ < Table . TD className = "text-gray-300" > { row . message } </ Table . TD >
357+ < Table . TD className = "-m-1 flex flex-wrap items-center justify-end" >
358+ { row . data && (
359+ < Tooltip
360+ content = { intl . formatMessage ( messages . viewdetails ) }
361+ >
362+ < Button
363+ buttonSize = "sm"
364+ buttonType = "primary"
365+ onClick = { ( ) =>
366+ setActiveLog ( { log : row , isOpen : true } )
367+ }
368+ className = "m-1"
369+ >
370+ < DocumentSearchIcon className = "icon-md" />
371+ </ Button >
372+ </ Tooltip >
373+ ) }
337374 < Tooltip
338- content = { intl . formatMessage ( messages . viewdetails ) }
375+ content = { intl . formatMessage ( messages . copyToClipboard ) }
339376 >
340377 < Button
341378 buttonType = "primary"
342379 buttonSize = "sm"
343- onClick = { ( ) =>
344- setActiveLog ( { log : row , isOpen : true } )
345- }
380+ onClick = { ( ) => copyLogString ( row ) }
346381 className = "m-1"
347382 >
348- < DocumentSearchIcon className = "icon-md" />
383+ < ClipboardCopyIcon className = "icon-md" />
349384 </ Button >
350385 </ Tooltip >
351- ) }
352- < Tooltip
353- content = { intl . formatMessage ( messages . copyToClipboard ) }
354- >
355- < Button
356- buttonType = "primary"
357- buttonSize = "sm"
358- onClick = { ( ) => copyLogString ( row ) }
359- className = "m-1"
360- >
361- < ClipboardCopyIcon className = "icon-md" />
362- </ Button >
363- </ Tooltip >
364- </ Table . TD >
365- </ tr >
366- ) ;
367- } ) }
386+ </ Table . TD >
387+ </ tr >
388+ ) ;
389+ } )
390+ ) }
368391
369- { data . results . length === 0 && (
392+ { data ? .results . length === 0 && (
370393 < tr className = "relative h-24 p-2 text-white" >
371394 < Table . TD colSpan = { 5 } noPadding >
372395 < div className = "flex w-screen flex-col items-center justify-center p-6 md:w-full" >
@@ -396,15 +419,15 @@ const SettingsLogs = () => {
396419 >
397420 < div className = "hidden lg:flex lg:flex-1" >
398421 < p className = "text-sm" >
399- { data . results . length > 0 &&
422+ { ( data ? .results . length ?? 0 ) > 0 &&
400423 intl . formatMessage ( globalMessages . showingresults , {
401424 from : pageIndex * currentPageSize + 1 ,
402425 to :
403- data . results . length < currentPageSize
426+ data ? .results . length ?? 0 < currentPageSize
404427 ? pageIndex * currentPageSize +
405- data . results . length
428+ ( data ? .results . length ?? 0 )
406429 : ( pageIndex + 1 ) * currentPageSize ,
407- total : data . pageInfo . results ,
430+ total : data ? .pageInfo . results ?? 0 ,
408431 strong : ( msg : React . ReactNode ) => (
409432 < span className = "font-medium" > { msg } </ span >
410433 ) ,
0 commit comments