github.com/pyroscope-io/pyroscope@v0.37.3-0.20230725203016-5f6947968bd0/webapp/javascript/components/Settings/Apps/index.tsx (about) 1 import React, { useEffect, useState } from 'react'; 2 import cl from 'classnames'; 3 import { useAppDispatch, useAppSelector } from '@webapp/redux/hooks'; 4 import { 5 selectApps, 6 reloadApps, 7 deleteApp, 8 selectIsLoadingApps, 9 } from '@webapp/redux/reducers/settings'; 10 import { addNotification } from '@webapp/redux/reducers/notifications'; 11 import { type App } from '@webapp/models/app'; 12 import Input from '@webapp/ui/Input'; 13 import TableUI from '@webapp/ui/Table'; 14 import LoadingSpinner from '@webapp/ui/LoadingSpinner'; 15 import { getAppTableRows } from './getAppTableRows'; 16 17 import appsStyles from './Apps.module.css'; 18 import tableStyles from '../SettingsTable.module.scss'; 19 20 const headRow = [ 21 { name: '', label: 'Name', sortable: 0 }, 22 { name: '', label: '', sortable: 0 }, 23 ]; 24 25 function Apps() { 26 const dispatch = useAppDispatch(); 27 const apps = useAppSelector(selectApps); 28 const isLoading = useAppSelector(selectIsLoadingApps); 29 const [search, setSearchField] = useState(''); 30 const [appsInProcessing, setAppsInProcessing] = useState<string[]>([]); 31 const [deletedApps, setDeletedApps] = useState<string[]>([]); 32 33 useEffect(() => { 34 dispatch(reloadApps()); 35 }, []); 36 37 const displayApps = 38 (apps && 39 apps.filter( 40 (x) => 41 x.name.toLowerCase().indexOf(search.toLowerCase()) !== -1 && 42 !deletedApps.includes(x.name) 43 )) || 44 []; 45 46 const handleDeleteApp = (app: App) => { 47 setAppsInProcessing([...appsInProcessing, app.name]); 48 dispatch(deleteApp(app)) 49 .unwrap() 50 .then(() => { 51 setAppsInProcessing(appsInProcessing.filter((x) => x !== app.name)); 52 setDeletedApps([...deletedApps, app.name]); 53 dispatch( 54 addNotification({ 55 type: 'success', 56 title: 'App has been deleted', 57 message: `App ${app.name} has been successfully deleted`, 58 }) 59 ); 60 }) 61 .catch(() => { 62 setDeletedApps(deletedApps.filter((x) => x !== app.name)); 63 setAppsInProcessing(appsInProcessing.filter((x) => x !== app.name)); 64 }); 65 }; 66 67 const tableBodyProps = 68 displayApps.length > 0 69 ? { 70 bodyRows: getAppTableRows( 71 displayApps, 72 appsInProcessing, 73 handleDeleteApp 74 ), 75 type: 'filled' as const, 76 } 77 : { 78 type: 'not-filled' as const, 79 value: 'The list is empty', 80 bodyClassName: appsStyles.appsTableEmptyMessage, 81 }; 82 83 return ( 84 <> 85 <h2 className={appsStyles.tabNameContrainer}> 86 Apps 87 {isLoading && !!apps ? <LoadingSpinner /> : null} 88 </h2> 89 <div className={appsStyles.searchContainer}> 90 <Input 91 type="text" 92 placeholder="Search app" 93 value={search} 94 onChange={(v) => setSearchField(v.target.value)} 95 name="Search app input" 96 /> 97 </div> 98 <TableUI 99 className={cl(appsStyles.appsTable, tableStyles.settingsTable)} 100 table={{ headRow, ...tableBodyProps }} 101 isLoading={isLoading && !apps} 102 /> 103 </> 104 ); 105 } 106 107 export default Apps;