diff --git a/frontend/src/Components/Error/ErrorBoundaryError.css b/frontend/src/Components/Error/ErrorBoundaryError.css index b6d1f917e..3e7a04302 100644 --- a/frontend/src/Components/Error/ErrorBoundaryError.css +++ b/frontend/src/Components/Error/ErrorBoundaryError.css @@ -25,6 +25,10 @@ white-space: pre-wrap; } +.version { + margin-top: 20px; +} + @media only screen and (max-width: $breakpointMedium) { .image { height: 250px; diff --git a/frontend/src/Components/Error/ErrorBoundaryError.css.d.ts b/frontend/src/Components/Error/ErrorBoundaryError.css.d.ts index 1d4e56b65..e19fd804d 100644 --- a/frontend/src/Components/Error/ErrorBoundaryError.css.d.ts +++ b/frontend/src/Components/Error/ErrorBoundaryError.css.d.ts @@ -6,6 +6,7 @@ interface CssExports { 'image': string; 'imageContainer': string; 'message': string; + 'version': string; } export const cssExports: CssExports; export default cssExports; diff --git a/frontend/src/Components/Error/ErrorBoundaryError.js b/frontend/src/Components/Error/ErrorBoundaryError.js deleted file mode 100644 index f99930437..000000000 --- a/frontend/src/Components/Error/ErrorBoundaryError.js +++ /dev/null @@ -1,60 +0,0 @@ -import PropTypes from 'prop-types'; -import React from 'react'; -import styles from './ErrorBoundaryError.css'; - -function ErrorBoundaryError(props) { - const { - className, - messageClassName, - detailsClassName, - message, - error, - info - } = props; - - return ( -
-
- {message} -
- -
- -
- -
- { - error && -
- {error.toString()} -
- } - -
- {info.componentStack} -
-
-
- ); -} - -ErrorBoundaryError.propTypes = { - className: PropTypes.string.isRequired, - messageClassName: PropTypes.string.isRequired, - detailsClassName: PropTypes.string.isRequired, - message: PropTypes.string.isRequired, - error: PropTypes.object.isRequired, - info: PropTypes.object.isRequired -}; - -ErrorBoundaryError.defaultProps = { - className: styles.container, - messageClassName: styles.message, - detailsClassName: styles.details, - message: 'There was an error loading this content' -}; - -export default ErrorBoundaryError; diff --git a/frontend/src/Components/Error/ErrorBoundaryError.tsx b/frontend/src/Components/Error/ErrorBoundaryError.tsx new file mode 100644 index 000000000..e7e74d10a --- /dev/null +++ b/frontend/src/Components/Error/ErrorBoundaryError.tsx @@ -0,0 +1,73 @@ +import React, { useEffect, useState } from 'react'; +import StackTrace from 'stacktrace-js'; +import translate from 'Utilities/String/translate'; +import styles from './ErrorBoundaryError.css'; + +interface ErrorBoundaryErrorProps { + className: string; + messageClassName: string; + detailsClassName: string; + message: string; + error: Error; + info: { + componentStack: string; + }; +} + +function ErrorBoundaryError(props: ErrorBoundaryErrorProps) { + const { + className = styles.container, + messageClassName = styles.message, + detailsClassName = styles.details, + message = translate('ErrorLoadingContent'), + error, + info, + } = props; + + const [detailedError, setDetailedError] = useState< + StackTrace.StackFrame[] | null + >(null); + + useEffect(() => { + if (error) { + StackTrace.fromError(error).then((de) => { + setDetailedError(de); + }); + } else { + setDetailedError(null); + } + }, [error, setDetailedError]); + + return ( +
+
{message}
+ +
+ +
+ +
+ {error ?
{error.message}
: null} + + {detailedError ? ( + detailedError.map((d, index) => { + return ( +
+ {` at ${d.functionName} (${d.fileName}:${d.lineNumber}:${d.columnNumber})`} +
+ ); + }) + ) : ( +
{info.componentStack}
+ )} + + {
Version: {window.Lidarr.version}
} +
+
+ ); +} + +export default ErrorBoundaryError; diff --git a/package.json b/package.json index df7c13a08..c64d18bf2 100644 --- a/package.json +++ b/package.json @@ -83,6 +83,7 @@ "redux-localstorage": "0.4.1", "redux-thunk": "2.3.0", "reselect": "4.1.8", + "stacktrace-js": "2.0.2", "typescript": "4.9.5" }, "devDependencies": { diff --git a/src/NzbDrone.Core/Localization/Core/en.json b/src/NzbDrone.Core/Localization/Core/en.json index c719d0edb..42d553a0d 100644 --- a/src/NzbDrone.Core/Localization/Core/en.json +++ b/src/NzbDrone.Core/Localization/Core/en.json @@ -341,6 +341,7 @@ "Episode": "Episode", "EpisodeDoesNotHaveAnAbsoluteEpisodeNumber": "Episode does not have an absolute episode number", "Error": "Error", + "ErrorLoadingContent": "There was an error loading this content", "ErrorLoadingContents": "Error loading contents", "ErrorLoadingPreviews": "Error loading previews", "ErrorRestoringBackup": "Error restoring backup", diff --git a/yarn.lock b/yarn.lock index f6a956df4..5458d5866 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2892,6 +2892,13 @@ error-ex@^1.3.1: dependencies: is-arrayish "^0.2.1" +error-stack-parser@^2.0.6: + version "2.1.4" + resolved "https://registry.yarnpkg.com/error-stack-parser/-/error-stack-parser-2.1.4.tgz#229cb01cdbfa84440bfa91876285b94680188286" + integrity sha512-Sk5V6wVazPhq5MhpO+AUxJn5x7XSXGl1R93Vn7i+zS15KDVxQijejNCrz8340/2bgLBjR9GtEG8ZVKONDjcqGQ== + dependencies: + stackframe "^1.3.4" + error@^7.0.0: version "7.2.1" resolved "https://registry.yarnpkg.com/error/-/error-7.2.1.tgz#eab21a4689b5f684fc83da84a0e390de82d94894" @@ -6028,6 +6035,11 @@ source-map-support@~0.5.20: buffer-from "^1.0.0" source-map "^0.6.0" +source-map@0.5.6: + version "0.5.6" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.6.tgz#75ce38f52bf0733c5a7f0c118d81334a2bb5f412" + integrity sha512-MjZkVp0NHr5+TPihLcadqnlVoGIoWo4IBHptutGh9wI3ttUYvCG26HkSuDi+K6lsZ25syXJXcctwgyVCt//xqA== + source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.0: version "0.6.1" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" @@ -6064,6 +6076,35 @@ spdx-license-ids@^3.0.0: resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.13.tgz#7189a474c46f8d47c7b0da4b987bb45e908bd2d5" integrity sha512-XkD+zwiqXHikFZm4AX/7JSCXA98U5Db4AFd5XUg/+9UNtnH75+Z9KxtpYiJZx36mUDVOwH83pl7yvCer6ewM3w== +stack-generator@^2.0.5: + version "2.0.10" + resolved "https://registry.yarnpkg.com/stack-generator/-/stack-generator-2.0.10.tgz#8ae171e985ed62287d4f1ed55a1633b3fb53bb4d" + integrity sha512-mwnua/hkqM6pF4k8SnmZ2zfETsRUpWXREfA/goT8SLCV4iOFa4bzOX2nDipWAZFPTjLvQB82f5yaodMVhK0yJQ== + dependencies: + stackframe "^1.3.4" + +stackframe@^1.3.4: + version "1.3.4" + resolved "https://registry.yarnpkg.com/stackframe/-/stackframe-1.3.4.tgz#b881a004c8c149a5e8efef37d51b16e412943310" + integrity sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw== + +stacktrace-gps@^3.0.4: + version "3.1.2" + resolved "https://registry.yarnpkg.com/stacktrace-gps/-/stacktrace-gps-3.1.2.tgz#0c40b24a9b119b20da4525c398795338966a2fb0" + integrity sha512-GcUgbO4Jsqqg6RxfyTHFiPxdPqF+3LFmQhm7MgCuYQOYuWyqxo5pwRPz5d/u6/WYJdEnWfK4r+jGbyD8TSggXQ== + dependencies: + source-map "0.5.6" + stackframe "^1.3.4" + +stacktrace-js@2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/stacktrace-js/-/stacktrace-js-2.0.2.tgz#4ca93ea9f494752d55709a081d400fdaebee897b" + integrity sha512-Je5vBeY4S1r/RnLydLl0TBTi3F2qdfWmYsGvtfZgEI+SCprPppaIhQf5nGcal4gI4cGpCV/duLcAzT1np6sQqg== + dependencies: + error-stack-parser "^2.0.6" + stack-generator "^2.0.5" + stacktrace-gps "^3.0.4" + streamqueue@1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/streamqueue/-/streamqueue-1.1.2.tgz#6c99c7c20d62b57f5819296bf9ec942542380192"