add middleware to handle 403 errors (#5245)

* add middleware to handle 403 errors

* remove log

* add logic to warn the user if not all requested images could be deleted

* lint

* fix copy

* feat(ui): simplify batchEnqueuedListener error toast logic

* feat(ui): use translations for error messages

* chore(ui): lint

---------

Co-authored-by: Mary Hipp <maryhipp@Marys-MacBook-Air.local>
Co-authored-by: psychedelicious <4822129+psychedelicious@users.noreply.github.com>
This commit is contained in:
Mary Hipp Rogers 2023-12-07 19:26:15 -05:00 committed by GitHub
parent db58efbe65
commit b519b6e1e0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 60 additions and 50 deletions

View File

@ -161,6 +161,7 @@
"txt2img": "Text To Image",
"unifiedCanvas": "Unified Canvas",
"unknown": "Unknown",
"unknownError": "Unknown Error",
"upload": "Upload"
},
"controlnet": {
@ -384,7 +385,9 @@
"deleteSelection": "Delete Selection",
"downloadSelection": "Download Selection",
"preparingDownload": "Preparing Download",
"preparingDownloadFailed": "Problem Preparing Download"
"preparingDownloadFailed": "Problem Preparing Download",
"problemDeletingImages": "Problem Deleting Images",
"problemDeletingImagesDesc": "One or more images could not be deleted"
},
"hotkeys": {
"acceptStagingImage": {

View File

@ -3,7 +3,7 @@ import { logger } from 'app/logging/logger';
import { parseify } from 'common/util/serialize';
import { zPydanticValidationError } from 'features/system/store/zodSchemas';
import { t } from 'i18next';
import { get, truncate, upperFirst } from 'lodash-es';
import { truncate, upperFirst } from 'lodash-es';
import { queueApi } from 'services/api/endpoints/queue';
import { TOAST_OPTIONS, theme } from 'theme/theme';
import { startAppListening } from '..';
@ -74,22 +74,11 @@ export const addBatchEnqueuedListener = () => {
),
});
});
} else {
let detail = 'Unknown Error';
let duration = undefined;
if (response.status === 403 && 'body' in response) {
detail = get(response, 'body.detail', 'Unknown Error');
} else if (response.status === 403 && 'error' in response) {
detail = get(response, 'error.detail', 'Unknown Error');
} else if (response.status === 403 && 'data' in response) {
detail = get(response, 'data.detail', 'Unknown Error');
duration = 15000;
}
} else if (response.status !== 403) {
toast({
title: t('queue.batchFailedToQueue'),
description: t('common.unknownError'),
status: 'error',
description: detail,
...(duration ? { duration } : {}),
});
}
logger('queue').error(

View File

@ -109,20 +109,9 @@ export const addControlNetImageProcessedListener = () => {
t('queue.graphFailedToQueue')
);
// handle usage-related errors
if (error instanceof Object) {
if ('data' in error && 'status' in error) {
if (error.status === 403) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const detail = (error.data as any)?.detail || 'Unknown Error';
dispatch(
addToast({
title: t('queue.graphFailedToQueue'),
status: 'error',
description: detail,
duration: 15000,
})
);
dispatch(pendingControlImagesCleared());
dispatch(controlAdapterImageChanged({ id, controlImage: null }));
return;

View File

@ -75,31 +75,20 @@ export const addUpscaleRequestedListener = () => {
t('queue.graphFailedToQueue')
);
// handle usage-related errors
if (error instanceof Object) {
if ('data' in error && 'status' in error) {
if (error.status === 403) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const detail = (error.data as any)?.detail || 'Unknown Error';
dispatch(
addToast({
title: t('queue.graphFailedToQueue'),
status: 'error',
description: detail,
duration: 15000,
})
);
return;
}
}
if (
error instanceof Object &&
'status' in error &&
error.status === 403
) {
return;
} else {
dispatch(
addToast({
title: t('queue.graphFailedToQueue'),
status: 'error',
})
);
}
dispatch(
addToast({
title: t('queue.graphFailedToQueue'),
status: 'error',
})
);
}
},
});

View File

@ -33,6 +33,7 @@ import { actionsDenylist } from './middleware/devtools/actionsDenylist';
import { stateSanitizer } from './middleware/devtools/stateSanitizer';
import { listenerMiddleware } from './middleware/listenerMiddleware';
import { createStore as createIDBKeyValStore, get, set } from 'idb-keyval';
import { authToastMiddleware } from 'services/api/authToastMiddleware';
const allReducers = {
canvas: canvasReducer,
@ -107,6 +108,7 @@ export const createStore = (uniqueStoreKey?: string) =>
})
.concat(api.middleware)
.concat(dynamicMiddlewares)
.concat(authToastMiddleware)
.prepend(listenerMiddleware.middleware),
devTools: {
actionSanitizer,

View File

@ -0,0 +1,26 @@
import { isRejectedWithValue } from '@reduxjs/toolkit';
import type { MiddlewareAPI, Middleware } from '@reduxjs/toolkit';
import { addToast } from 'features/system/store/systemSlice';
import { t } from 'i18next';
export const authToastMiddleware: Middleware =
(api: MiddlewareAPI) => (next) => (action) => {
if (isRejectedWithValue(action)) {
if (action.payload.status === 403) {
const { dispatch } = api;
const customMessage =
action.payload.data.detail !== 'Forbidden'
? action.payload.data.detail
: undefined;
dispatch(
addToast({
title: t('common.somethingWentWrong'),
status: 'error',
description: customMessage,
})
);
}
}
return next(action);
};

View File

@ -27,6 +27,8 @@ import {
imagesSelectors,
} from 'services/api/util';
import { boardsApi } from './boards';
import { addToast } from 'features/system/store/systemSlice';
import { t } from 'i18next';
export const imagesApi = api.injectEndpoints({
endpoints: (build) => ({
@ -208,6 +210,16 @@ export const imagesApi = api.injectEndpoints({
try {
const { data } = await queryFulfilled;
if (data.deleted_images.length < imageDTOs.length) {
dispatch(
addToast({
title: t('gallery.problemDeletingImages'),
description: t('gallery.problemDeletingImagesDesc'),
status: 'warning',
})
);
}
// convert to an object so we can access the successfully delete image DTOs by name
const groupedImageDTOs = keyBy(imageDTOs, 'image_name');