diff --git a/electron/main.js b/electron/main.js index 7809487e3..c30d7b3e2 100644 --- a/electron/main.js +++ b/electron/main.js @@ -554,6 +554,19 @@ ipcMain.on('open-path', (_, filePath) => { shell.openPath(filePath); }); +function getSanitizedLogs({ name, filePath, data }) { + const logs = filePath ? fs.readFileSync(filePath, 'utf-8') : data; + const tempDir = os.tmpdir(); + + const usernameRegex = /\/Users\/([^/]+)/g; + const sanitizedData = logs.replace(usernameRegex, '/Users/*****'); + + const sanitizedLogsFilePath = path.join(tempDir, name); + fs.writeFileSync(sanitizedLogsFilePath, sanitizedData); + + return sanitizedLogsFilePath; +} + // EXPORT LOGS ipcMain.handle('save-logs', async (_, data) => { // version.txt @@ -631,16 +644,3 @@ ipcMain.handle('save-logs', async (_, data) => { return result; }); - -function getSanitizedLogs({ name, filePath, data }) { - const logs = filePath ? fs.readFileSync(filePath, 'utf-8') : data; - const tempDir = os.tmpdir(); - - const usernameRegex = /\/Users\/([^/]+)/g; - const sanitizedData = logs.replace(usernameRegex, '/Users/*****'); - - const sanitizedLogsFilePath = path.join(tempDir, name); - fs.writeFileSync(sanitizedLogsFilePath, sanitizedData); - - return sanitizedLogsFilePath; -} diff --git a/frontend/components/HelpAndSupport/HelpAndSupport.tsx b/frontend/components/HelpAndSupport/HelpAndSupport.tsx index e26cedd63..431d1994e 100644 --- a/frontend/components/HelpAndSupport/HelpAndSupport.tsx +++ b/frontend/components/HelpAndSupport/HelpAndSupport.tsx @@ -2,14 +2,11 @@ import { CloseOutlined, QuestionCircleOutlined } from '@ant-design/icons'; import { Button, Card, Flex, message, Typography } from 'antd'; import { useCallback, useEffect, useState } from 'react'; -import { DeploymentStatus } from '@/client'; import { FAQ_URL, SUPPORT_URL } from '@/constants'; import { UNICODE_SYMBOLS } from '@/constants/unicode'; import { PageState } from '@/enums'; -import { useBalance, usePageState, useServices } from '@/hooks'; +import { useLogs, usePageState } from '@/hooks'; import { useElectronApi } from '@/hooks/useElectronApi'; -import { useMasterSafe } from '@/hooks/useMasterSafe'; -import { useStore } from '@/hooks/useStore'; import { CardTitle } from '../common/CardTitle'; import { CardSection } from '../styled/CardSection'; @@ -27,118 +24,50 @@ const SettingsTitle = () => ( /> ); +const LogsSavedMessage = ({ onClick }: { onClick: () => void }) => { + return ( + + Logs saved + + + ); +}; + export const HelpAndSupport = () => { const { goto } = usePageState(); - const { saveLogs, openPath } = useElectronApi(); - - const { storeState } = useStore(); - const { - serviceStatus, - services, - hasInitialLoaded: isServiceLoaded, - } = useServices(); - const { - isBalanceLoaded, - totalEthBalance, - totalOlasBalance, - wallets, - walletBalances, - totalOlasStakedBalance, - } = useBalance(); + const { openPath, saveLogs } = useElectronApi(); - const { - backupSafeAddress, - masterSafeAddress, - masterEoaAddress, - masterSafeOwners, - } = useMasterSafe(); + const logs = useLogs(); const [isLoading, setIsLoading] = useState(false); - const [canSaveLogs, setCanSafeLogs] = useState(false); + const [canSaveLogs, setCanSaveLogs] = useState(false); - const onSaveLogs = useCallback(() => { - setIsLoading(true); - setCanSafeLogs(true); - }, []); - - const handleSaveLogs = useCallback(() => { - return saveLogs?.({ - store: storeState, - debugData: { - services: { - services: - services?.map((item) => ({ - ...item, - keys: item.keys.map((key) => key.address), - })) ?? 'undefined', - serviceStatus: serviceStatus - ? DeploymentStatus[serviceStatus] - : 'undefined', - }, - addresses: [ - { backupSafeAddress: backupSafeAddress ?? 'undefined' }, - { masterSafeAddress: masterSafeAddress ?? 'undefined' }, - { masterEoaAddress: masterEoaAddress ?? 'undefined' }, - { masterSafeOwners: masterSafeOwners ?? 'undefined' }, - ], - balances: [ - { wallets: wallets ?? 'undefined' }, - { walletBalances: walletBalances ?? 'undefined' }, - { totalOlasStakedBalance: totalOlasStakedBalance ?? 'undefined' }, - { totalEthBalance: totalEthBalance ?? 'undefined' }, - { totalOlasBalance: totalOlasBalance ?? 'undefined' }, - ], - }, - }).then((result) => { - if (result.success) { - message.success({ - content: ( - - Logs saved to: - - - ), - duration: 10, - }); - } else { - message.error('Save logs failed or cancelled'); - } - }); - }, [ - backupSafeAddress, - masterEoaAddress, - masterSafeAddress, - masterSafeOwners, - openPath, - saveLogs, - serviceStatus, - services, - storeState, - totalEthBalance, - totalOlasBalance, - totalOlasStakedBalance, - walletBalances, - wallets, - ]); + const onSaveLogs = useCallback(() => setCanSaveLogs(true), []); useEffect(() => { - // only save logs when all needed data is loaded - if (canSaveLogs && isBalanceLoaded && isServiceLoaded) { - handleSaveLogs()?.finally(() => { - setIsLoading(false); - setCanSafeLogs(false); - }); + if (canSaveLogs && logs && !isLoading) { + setIsLoading(true); + saveLogs?.(logs) + .then((result) => { + if (result.success) { + message.success({ + content: ( + openPath?.(result.dirPath)} /> + ), + duration: 10, + }); + } else { + message.error('Save logs failed or cancelled'); + } + }) + .finally(() => { + setIsLoading(false); + setCanSaveLogs(false); + }); } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [canSaveLogs, isBalanceLoaded, isServiceLoaded]); + }, [canSaveLogs, isLoading, logs, openPath, saveLogs]); return ( { type="primary" ghost size="large" - loading={isLoading} + loading={isLoading || canSaveLogs} onClick={onSaveLogs} > Export logs diff --git a/frontend/context/ElectronApiProvider.tsx b/frontend/context/ElectronApiProvider.tsx index 497374597..64013aff5 100644 --- a/frontend/context/ElectronApiProvider.tsx +++ b/frontend/context/ElectronApiProvider.tsx @@ -28,7 +28,7 @@ type ElectronApiContextProps = { saveLogs?: (data: { store?: ElectronStore; debugData?: Record; - }) => Promise<{ success?: boolean; dirPath?: string }>; + }) => Promise<{ success: true; dirPath: string } | { success: false }>; openPath?: (filePath: string) => void; }; diff --git a/frontend/hooks/index.ts b/frontend/hooks/index.ts index 98d7f6ca3..3f43ba7ba 100644 --- a/frontend/hooks/index.ts +++ b/frontend/hooks/index.ts @@ -1,4 +1,5 @@ export * from './useBalance'; +export * from './useLogs'; export * from './usePageState'; export * from './useServices'; export * from './useServiceTemplates'; diff --git a/frontend/hooks/useLogs.ts b/frontend/hooks/useLogs.ts new file mode 100644 index 000000000..b72e59c3b --- /dev/null +++ b/frontend/hooks/useLogs.ts @@ -0,0 +1,96 @@ +import { useMemo } from 'react'; + +import { DeploymentStatus } from '@/client'; + +import { useBalance } from './useBalance'; +import { useMasterSafe } from './useMasterSafe'; +import { useServices } from './useServices'; +import { useStore } from './useStore'; +import { useWallet } from './useWallet'; + +const useAddressesLogs = () => { + const { wallets, masterEoaAddress, masterSafeAddress } = useWallet(); + + const { backupSafeAddress, masterSafeOwners } = useMasterSafe(); + + return { + isLoaded: wallets?.length !== 0 && !!masterSafeOwners, + data: [ + { backupSafeAddress: backupSafeAddress ?? 'undefined' }, + { masterSafeAddress: masterSafeAddress ?? 'undefined' }, + { masterEoaAddress: masterEoaAddress ?? 'undefined' }, + { masterSafeOwners: masterSafeOwners ?? 'undefined' }, + ], + }; +}; + +const useBalancesLogs = () => { + const { + isBalanceLoaded, + totalEthBalance, + totalOlasBalance, + wallets, + walletBalances, + totalOlasStakedBalance, + } = useBalance(); + + return { + isLoaded: isBalanceLoaded, + data: [ + { wallets: wallets ?? 'undefined' }, + { walletBalances: walletBalances ?? 'undefined' }, + { totalOlasStakedBalance: totalOlasStakedBalance ?? 'undefined' }, + { totalEthBalance: totalEthBalance ?? 'undefined' }, + { totalOlasBalance: totalOlasBalance ?? 'undefined' }, + ], + }; +}; + +const useServicesLogs = () => { + const { serviceStatus, services, hasInitialLoaded } = useServices(); + + return { + isLoaded: hasInitialLoaded, + data: { + serviceStatus: serviceStatus + ? DeploymentStatus[serviceStatus] + : 'undefined', + services: + services?.map((item) => ({ + ...item, + keys: item.keys.map((key) => key.address), + })) ?? 'undefined', + }, + }; +}; + +export const useLogs = () => { + const { storeState } = useStore(); + + const { isLoaded: isServicesLoaded, data: services } = useServicesLogs(); + const { isLoaded: isBalancesLoaded, data: balances } = useBalancesLogs(); + const { isLoaded: isAddressesLoaded, data: addresses } = useAddressesLogs(); + + const logs = useMemo(() => { + if (isServicesLoaded && isBalancesLoaded && isAddressesLoaded) { + return { + store: storeState, + debugData: { + services, + addresses, + balances, + }, + }; + } + }, [ + addresses, + balances, + isAddressesLoaded, + isBalancesLoaded, + isServicesLoaded, + services, + storeState, + ]); + + return logs; +};