refactor(dashboard): use onError on server actions

This commit is contained in:
Nicolas Meienberger 2023-12-06 21:52:49 +01:00
parent 25c39b92e5
commit 76f9637d77
12 changed files with 106 additions and 83 deletions

View file

@ -1,4 +1,4 @@
import { SocketEvent } from '@runtipi/shared'; import { SocketEvent } from '@runtipi/shared/src/schemas/socket';
import { Socket } from 'socket.io'; import { Socket } from 'socket.io';
import { logger } from '../logger'; import { logger } from '../logger';

View file

@ -3,7 +3,8 @@
import fs from 'fs'; import fs from 'fs';
import path from 'path'; import path from 'path';
import pg from 'pg'; import pg from 'pg';
import { execAsync, pathExists, SocketEvent } from '@runtipi/shared'; import { execAsync, pathExists } from '@runtipi/shared';
import { SocketEvent } from '@runtipi/shared/src/schemas/socket';
import { copyDataDir, generateEnvFile } from './app.helpers'; import { copyDataDir, generateEnvFile } from './app.helpers';
import { logger } from '@/lib/logger'; import { logger } from '@/lib/logger';
import { compose } from '@/lib/docker'; import { compose } from '@/lib/docker';
@ -34,7 +35,7 @@ export class AppExecutors {
this.logger = logger; this.logger = logger;
} }
private handleAppError = (err: unknown, appId: string, event: SocketEvent['event']) => { private handleAppError = (err: unknown, appId: string, event: Extract<SocketEvent, { type: 'app' }>['event']) => {
if (err instanceof Error) { if (err instanceof Error) {
SocketManager.emit({ type: 'app', event, data: { appId, error: err.message } }); SocketManager.emit({ type: 'app', event, data: { appId, error: err.message } });
this.logger.error(`An error occurred: ${err.message}`); this.logger.error(`An error occurred: ${err.message}`);

View file

@ -4209,6 +4209,11 @@ packages:
/base64-js@1.5.1: /base64-js@1.5.1:
resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==}
/base64id@2.0.0:
resolution: {integrity: sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==}
engines: {node: ^4.5.0 || >= 5.9}
dev: false
/binary-extensions@2.2.0: /binary-extensions@2.2.0:
resolution: {integrity: sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==} resolution: {integrity: sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==}
engines: {node: '>=8'} engines: {node: '>=8'}
@ -4673,6 +4678,14 @@ packages:
resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==}
dev: true dev: true
/cors@2.8.5:
resolution: {integrity: sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==}
engines: {node: '>= 0.10'}
dependencies:
object-assign: 4.1.1
vary: 1.1.2
dev: false
/cosmiconfig@7.1.0: /cosmiconfig@7.1.0:
resolution: {integrity: sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==} resolution: {integrity: sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==}
engines: {node: '>=10'} engines: {node: '>=10'}

View file

@ -14,10 +14,11 @@ export function LoginContainer() {
const router = useRouter(); const router = useRouter();
const loginMutation = useAction(loginAction, { const loginMutation = useAction(loginAction, {
onError: (e) => {
if (e.serverError) toast.error(e.serverError);
},
onSuccess: (data) => { onSuccess: (data) => {
if (!data.success) { if (data.success && data.totpSessionId) {
toast.error(data.failure.reason);
} else if (data.success && data.totpSessionId) {
setTotpSessionId(data.totpSessionId); setTotpSessionId(data.totpSessionId);
} else { } else {
router.push('/dashboard'); router.push('/dashboard');
@ -26,18 +27,27 @@ export function LoginContainer() {
}); });
const verifyTotpMutation = useAction(verifyTotpAction, { const verifyTotpMutation = useAction(verifyTotpAction, {
onSuccess: (data) => { onError: (e) => {
if (!data.success) { if (e.serverError) toast.error(e.serverError);
toast.error(data.failure.reason); },
} else { onSuccess: () => {
router.push('/dashboard'); router.push('/dashboard');
}
}, },
}); });
if (totpSessionId) { if (totpSessionId) {
return <TotpForm loading={verifyTotpMutation.status === 'executing'} onSubmit={(totpCode) => verifyTotpMutation.execute({ totpCode, totpSessionId })} />; return (
<TotpForm
loading={verifyTotpMutation.status === 'executing'}
onSubmit={(totpCode) => verifyTotpMutation.execute({ totpCode, totpSessionId })}
/>
);
} }
return <LoginForm loading={loginMutation.status === 'executing'} onSubmit={({ email, password }) => loginMutation.execute({ username: email, password })} />; return (
<LoginForm
loading={loginMutation.status === 'executing'}
onSubmit={({ email, password }) => loginMutation.execute({ username: email, password })}
/>
);
} }

View file

@ -11,14 +11,18 @@ export const RegisterContainer: React.FC = () => {
const router = useRouter(); const router = useRouter();
const registerMutation = useAction(registerAction, { const registerMutation = useAction(registerAction, {
onSuccess: (data) => { onError: (e) => {
if (!data.success) { if (e.serverError) toast.error(e.serverError);
toast.error(data.failure.reason); },
} else { onSuccess: () => {
router.push('/dashboard'); router.push('/dashboard');
}
}, },
}); });
return <RegisterForm onSubmit={({ email, password }) => registerMutation.execute({ username: email, password })} loading={registerMutation.status === 'executing'} />; return (
<RegisterForm
onSubmit={({ email, password }) => registerMutation.execute({ username: email, password })}
loading={registerMutation.status === 'executing'}
/>
);
}; };

View file

@ -15,18 +15,14 @@ export const ResetPasswordContainer: React.FC = () => {
const router = useRouter(); const router = useRouter();
const resetPasswordMutation = useAction(resetPasswordAction, { const resetPasswordMutation = useAction(resetPasswordAction, {
onSuccess: (data) => { onError: (error) => {
if (!data.success) { if (error.serverError) toast.error(error.serverError);
toast.error(data.failure.reason);
}
}, },
}); });
const cancelRequestMutation = useAction(cancelResetPasswordAction, { const cancelRequestMutation = useAction(cancelResetPasswordAction, {
onSuccess: (data) => { onError: (error) => {
if (!data.success) { if (error.serverError) toast.error(error.serverError);
toast.error(data.failure.reason);
}
}, },
}); });

View file

@ -45,7 +45,7 @@ export const AppDetailsContainer: React.FC<AppDetailsContainerProps> = ({ app, l
const installMutation = useAction(installAppAction, { const installMutation = useAction(installAppAction, {
onError: (e) => { onError: (e) => {
toast.error(e.serverError!); if (e.serverError) toast.error(e.serverError);
}, },
onExecute: () => { onExecute: () => {
setOptimisticStatus('installing'); setOptimisticStatus('installing');
@ -55,7 +55,7 @@ export const AppDetailsContainer: React.FC<AppDetailsContainerProps> = ({ app, l
const uninstallMutation = useAction(uninstallAppAction, { const uninstallMutation = useAction(uninstallAppAction, {
onError: (e) => { onError: (e) => {
toast.error(e.serverError!); if (e.serverError) toast.error(e.serverError);
}, },
onExecute: () => { onExecute: () => {
uninstallDisclosure.close(); uninstallDisclosure.close();
@ -65,7 +65,7 @@ export const AppDetailsContainer: React.FC<AppDetailsContainerProps> = ({ app, l
const stopMutation = useAction(stopAppAction, { const stopMutation = useAction(stopAppAction, {
onError: (e) => { onError: (e) => {
toast.error(e.serverError!); if (e.serverError) toast.error(e.serverError);
}, },
onExecute: () => { onExecute: () => {
stopDisclosure.close(); stopDisclosure.close();
@ -75,7 +75,7 @@ export const AppDetailsContainer: React.FC<AppDetailsContainerProps> = ({ app, l
const startMutation = useAction(startAppAction, { const startMutation = useAction(startAppAction, {
onError: (e) => { onError: (e) => {
toast.error(e.serverError!); if (e.serverError) toast.error(e.serverError);
}, },
onExecute: () => { onExecute: () => {
setOptimisticStatus('starting'); setOptimisticStatus('starting');
@ -84,7 +84,7 @@ export const AppDetailsContainer: React.FC<AppDetailsContainerProps> = ({ app, l
const updateMutation = useAction(updateAppAction, { const updateMutation = useAction(updateAppAction, {
onError: (e) => { onError: (e) => {
toast.error(e.serverError!); if (e.serverError) toast.error(e.serverError);
}, },
onExecute: () => { onExecute: () => {
updateDisclosure.close(); updateDisclosure.close();
@ -94,7 +94,7 @@ export const AppDetailsContainer: React.FC<AppDetailsContainerProps> = ({ app, l
const updateConfigMutation = useAction(updateAppConfigAction, { const updateConfigMutation = useAction(updateAppConfigAction, {
onError: (e) => { onError: (e) => {
toast.error(e.serverError!); if (e.serverError) toast.error(e.serverError);
}, },
onExecute: () => { onExecute: () => {
updateSettingsDisclosure.close(); updateSettingsDisclosure.close();
@ -106,7 +106,7 @@ export const AppDetailsContainer: React.FC<AppDetailsContainerProps> = ({ app, l
const resetMutation = useAction(resetAppAction, { const resetMutation = useAction(resetAppAction, {
onError: (e) => { onError: (e) => {
toast.error(e.serverError!); if (e.serverError) toast.error(e.serverError);
}, },
onExecute: () => { onExecute: () => {
resetAppDisclosure.open(); resetAppDisclosure.open();

View file

@ -34,13 +34,12 @@ export const ChangePasswordForm = () => {
const router = useRouter(); const router = useRouter();
const changePasswordMutation = useAction(changePasswordAction, { const changePasswordMutation = useAction(changePasswordAction, {
onSuccess: (data) => { onError: (e) => {
if (!data.success) { if (e.serverError) toast.error(e.serverError);
toast.error(data.failure.reason); },
} else { onSuccess: () => {
toast.success(t('password-change-success')); toast.success(t('password-change-success'));
router.push('/'); router.push('/');
}
}, },
}); });

View file

@ -27,13 +27,12 @@ export const ChangeUsernameForm = ({ username }: Props) => {
type FormValues = z.infer<typeof schema>; type FormValues = z.infer<typeof schema>;
const changeUsernameMutation = useAction(changeUsernameAction, { const changeUsernameMutation = useAction(changeUsernameAction, {
onSuccess: (data) => { onError: (e) => {
if (!data.success) { if (e.serverError) toast.error(e.serverError);
toast.error(data.failure.reason); },
} else { onSuccess: () => {
toast.success(t('change-username.success')); toast.success(t('change-username.success'));
router.push('/'); router.push('/');
}
}, },
}); });

View file

@ -29,28 +29,26 @@ export const OtpForm = (props: { totpEnabled: boolean }) => {
onExecute: () => { onExecute: () => {
setupOtpDisclosure.close(); setupOtpDisclosure.close();
}, },
onError: (e) => {
setPassword('');
if (e.serverError) toast.error(e.serverError);
},
onSuccess: (data) => { onSuccess: (data) => {
if (!data.success) { setKey(data.key);
setPassword(''); setUri(data.uri);
toast.error(data.failure.reason);
} else {
setKey(data.key);
setUri(data.uri);
}
}, },
}); });
const setupTotpMutation = useAction(setupTotpAction, { const setupTotpMutation = useAction(setupTotpAction, {
onSuccess: (data) => { onError: (e) => {
if (!data.success) { setTotpCode('');
setTotpCode(''); if (e.serverError) toast.error(e.serverError);
toast.error(data.failure.reason); },
} else { onSuccess: () => {
setTotpCode(''); setTotpCode('');
setKey(''); setKey('');
setUri(''); setUri('');
toast.success(t('2fa-enable-success')); toast.success(t('2fa-enable-success'));
}
}, },
}); });
@ -58,13 +56,12 @@ export const OtpForm = (props: { totpEnabled: boolean }) => {
onExecute: () => { onExecute: () => {
disableOtpDisclosure.close(); disableOtpDisclosure.close();
}, },
onSuccess: (data) => { onError: (e) => {
if (!data.success) { setPassword('');
setPassword(''); if (e.serverError) toast.error(e.serverError);
toast.error(data.failure.reason); },
} else { onSuccess: () => {
toast.success(t('2fa-disable-success')); toast.success(t('2fa-disable-success'));
}
}, },
}); });

View file

@ -20,13 +20,12 @@ export const SettingsContainer = ({ initialValues, currentLocale }: Props) => {
const router = useRouter(); const router = useRouter();
const updateSettingsMutation = useAction(updateSettingsAction, { const updateSettingsMutation = useAction(updateSettingsAction, {
onSuccess: (data) => { onError: (e) => {
if (!data.success) { if (e.serverError) toast.error(e.serverError);
toast.error(data.failure.reason); },
} else { onSuccess: () => {
toast.success(t('settings.settings.settings-updated')); toast.success(t('settings.settings.settings-updated'));
router.refresh(); router.refresh();
}
}, },
}); });
@ -36,7 +35,12 @@ export const SettingsContainer = ({ initialValues, currentLocale }: Props) => {
return ( return (
<div className="card-body"> <div className="card-body">
<SettingsForm initalValues={initialValues} currentLocale={currentLocale} loading={updateSettingsMutation.status === 'executing'} onSubmit={onSubmit} /> <SettingsForm
initalValues={initialValues}
currentLocale={currentLocale}
loading={updateSettingsMutation.status === 'executing'}
onSubmit={onSubmit}
/>
</div> </div>
); );
}; };

View file

@ -6,7 +6,7 @@ export const action = createSafeActionClient({
console.error('Error from server', e); console.error('Error from server', e);
return { return {
serverError: e.message, serverError: e.message || 'An unexpected error occurred',
}; };
}, },
}); });