diff --git a/package-lock.json b/package-lock.json index 48f09c3f8290dacf5100da35debf1a9ec1adc030..99776d2b0b03948c57b84a919c2da89303b2009e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,7 +11,7 @@ "@ahooksjs/use-url-state": "^3.5.0", "@emotion/react": "^11.11.1", "@emotion/styled": "^11.11.0", - "@ess-ics/ce-ui-common": "^0.3.0", + "@ess-ics/ce-ui-common": "^0.3.3", "@fontsource/roboto": "^4.1.0", "@mui/icons-material": "^5.14.1", "@mui/material": "^5.14.1", @@ -2657,9 +2657,9 @@ } }, "node_modules/@ess-ics/ce-ui-common": { - "version": "0.3.0", - "resolved": "https://artifactory.esss.lu.se/artifactory/api/npm/ics-npm/@ess-ics/ce-ui-common/-/ce-ui-common-0.3.0.tgz", - "integrity": "sha1-WotEgtEAtZmNR3jsLMjupuz1gWE=", + "version": "0.3.3", + "resolved": "https://artifactory.esss.lu.se/artifactory/api/npm/ics-npm/@ess-ics/ce-ui-common/-/ce-ui-common-0.3.3.tgz", + "integrity": "sha1-7ExgDtf0QXJ7uli5f+WM16kbNAE=", "dependencies": { "@fontsource/titillium-web": "^4.5.9", "@mui/x-data-grid-pro": "^6.5.0", @@ -41647,9 +41647,9 @@ "dev": true }, "@ess-ics/ce-ui-common": { - "version": "0.3.0", - "resolved": "https://artifactory.esss.lu.se/artifactory/api/npm/ics-npm/@ess-ics/ce-ui-common/-/ce-ui-common-0.3.0.tgz", - "integrity": "sha1-WotEgtEAtZmNR3jsLMjupuz1gWE=", + "version": "0.3.3", + "resolved": "https://artifactory.esss.lu.se/artifactory/api/npm/ics-npm/@ess-ics/ce-ui-common/-/ce-ui-common-0.3.3.tgz", + "integrity": "sha1-7ExgDtf0QXJ7uli5f+WM16kbNAE=", "requires": { "@fontsource/titillium-web": "^4.5.9", "@mui/x-data-grid-pro": "^6.5.0", diff --git a/package.json b/package.json index 6a4142dbca8aae2b0e9cfd9c6a4cd88052cbe9a6..93436db1d243765c1509b5b25b1ca0d997ec8680 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,7 @@ "@ahooksjs/use-url-state": "^3.5.0", "@emotion/react": "^11.11.1", "@emotion/styled": "^11.11.0", - "@ess-ics/ce-ui-common": "^0.3.0", + "@ess-ics/ce-ui-common": "^0.3.3", "@fontsource/roboto": "^4.1.0", "@mui/icons-material": "^5.14.1", "@mui/material": "^5.14.1", diff --git a/public/config.js b/public/config.js index 2523bb0c470f3af0a3598b3e9f74a154011297a0..775bd5a904f151b3f0b42f651f2f348b761d5df7 100644 --- a/public/config.js +++ b/public/config.js @@ -6,4 +6,4 @@ TOKEN_RENEW_INTERVAL=180000 NAMING_ADDRESS='https://naming.esss.lu.se' FRONTEND_VERSION='0.0.0-local' FRONTEND_REPOSITORY_TAGS='https://gitlab.esss.lu.se/ccce/dev/ce-deploy-ui/-/tags/' -SUPPORT_URL='https://jira.esss.lu.se/plugins/servlet/desk/portal/44?requestGroup=137' \ No newline at end of file +SUPPORT_URL='https://jira.esss.lu.se/plugins/servlet/desk/portal/44?requestGroup=137' diff --git a/src/App.js b/src/App.js index 47adf6b0e408d234fdb88d0b259acc58ecf7ac7f..a8347f00f7de84a0f55df8a80d0e0d6a8e9ad4a8 100644 --- a/src/App.js +++ b/src/App.js @@ -19,12 +19,13 @@ import { HelpView } from "./views/help/HelpView"; import { SnackbarProvider } from "notistack"; import TokenRenew from "./components/auth/TokenRenew"; import { NotificationProvider } from "./components/common/notification/Notifications"; -import NotFound from "./components/navigation/NotFound"; -import { AppErrorBoundary } from "./components/navigation/ErrorBoundary/ErrorBoundary"; +import { NotFoundView } from "./components/navigation/NotFoundView"; +import { AppErrorBoundary } from "@ess-ics/ce-ui-common"; import { LoginView } from "./views/login/LoginView"; import { JobLogAccessControl } from "./views/jobs/JobLogAccessControl"; import { RecordListView } from "./views/records/RecordListView"; import { RecordDetailsView } from "./views/records/RecordDetailsView"; +import { TestErrorView } from "./views/TestErrorView"; import { GlobalAppBarContext } from "@ess-ics/ce-ui-common"; import { applicationTitle } from "./components/common/Helper"; import { UserPageView } from "./views/UserPage/UserPageView"; @@ -35,15 +36,15 @@ function App() { useEffect(() => setTitle(applicationTitle()), [setTitle]); return ( - <AppErrorBoundary> - <SnackbarProvider - preventDuplicate - maxSnack="5" - > - <StyledEngineProvider injectFirst> - <ThemeProvider theme={theme}> - <CssBaseline /> - <BrowserRouter> + <AppErrorBoundary supportHref={window.SUPPORT_URL}> + <BrowserRouter> + <SnackbarProvider + preventDuplicate + maxSnack="5" + > + <StyledEngineProvider injectFirst> + <ThemeProvider theme={theme}> + <CssBaseline /> <APIProvider> <UserProvider> <TokenRenew /> @@ -113,6 +114,11 @@ function App() { element={<LoginView />} exact /> + <Route + path="/error-test" + element={<TestErrorView />} + exact + /> <Route path="/user/:userName" element={<UserPageView />} @@ -120,7 +126,7 @@ function App() { /> <Route path="*" - element={<NotFound />} + element={<NotFoundView />} exact /> </Routes> @@ -128,10 +134,10 @@ function App() { </NotificationProvider> </UserProvider> </APIProvider> - </BrowserRouter> - </ThemeProvider> - </StyledEngineProvider> - </SnackbarProvider> + </ThemeProvider> + </StyledEngineProvider> + </SnackbarProvider> + </BrowserRouter> </AppErrorBoundary> ); } diff --git a/src/components/navigation/ErrorBoundary/ErrorBoundary.js b/src/components/navigation/ErrorBoundary/ErrorBoundary.js deleted file mode 100644 index 693bd2cac19b09c11a564d2b2ca84b7e472c7871..0000000000000000000000000000000000000000 --- a/src/components/navigation/ErrorBoundary/ErrorBoundary.js +++ /dev/null @@ -1,146 +0,0 @@ -import { Box, Button, Container, Grid, Typography } from "@mui/material"; -import { BugReport } from "@mui/icons-material"; -import React, { useCallback, useState } from "react"; -import { ErrorBoundary, useErrorHandler } from "react-error-boundary"; -import { useEffectOnMount } from "../../../hooks/MountEffects"; -import { theme } from "../../../style/Theme"; - -function AwSnap({ error, resetErrorBoundary }) { - const [showStack, setShowStack] = useState(false); - - const toggleStack = () => { - setShowStack(!showStack); - }; - - console.error("AwSnap", error); - return ( - <> - <Grid - container - justifyContent="center" - > - <BugReport - style={{ fontSize: 128, color: theme.palette.primary.main }} - /> - </Grid> - <Grid - container - justifyContent="center" - > - <h1 style={{ textAlign: "center" }}> - 💥 Whoops! Something went wrong 💥 - </h1> - </Grid> - <Grid - container - justifyContent="center" - > - <Typography variant="body1"> - If this error persists, please contact the{" "} - <a href="https://ess-eric.slack.com/archives/C01V3R31EP2"> - CCCE team - </a> - </Typography> - </Grid> - <Grid - container - justifyContent="center" - spacing={2} - > - <Grid item> - <Button - variant="contained" - color="secondary" - style={{ backgroundColor: theme.palette.primary.main }} - onClick={resetErrorBoundary} - > - Reload - </Button> - </Grid> - <Grid item> - <Button - variant="contained" - color="secondary" - style={{ backgroundColor: theme.palette.primary.main }} - onClick={toggleStack} - > - {showStack ? "Hide Details" : "Show details"} - </Button> - </Grid> - </Grid> - {showStack ? ( - <Grid - container - justifyContent="center" - > - <Box - p={10} - overflow="auto" - > - <pre>{error.stack}</pre> - </Box> - </Grid> - ) : null} - </> - ); -} - -function WindowErrorRedirect({ children }) { - const handleError = useErrorHandler(); - const onError = useCallback( - (event) => { - handleError(event.error ?? event.reason); - }, - [handleError] - ); - - useEffectOnMount(() => { - window.addEventListener("error", onError); - window.addEventListener("unhandledrejection", onError); - return function cleanup() { - window.removeEventListener("error", onError); - window.removeEventListener("unhandledrejection", onError); - }; - }); - - return children; -} - -export function AppErrorBoundary({ children }) { - return ( - <ErrorBoundary - FallbackComponent={AwSnap} - onReset={() => { - window.location.href = "/"; - }} - > - <WindowErrorRedirect>{children}</WindowErrorRedirect> - </ErrorBoundary> - ); -} - -export function Break() { - const [broken, setBroken] = useState(false); - const Bomb = ({ where }) => { - throw new Error(`KABOOM: You broke the UI ${where}`); - }; - return ( - <Container> - <Button - variant="contained" - onClick={() => setBroken(true)} - > - Break the UI during render - </Button> - {broken && <Bomb where="during Render" />} - <Button - variant="contained" - onClick={(event) => { - Bomb({ where: "in a Callback" }); - }} - > - Break the UI asynchronously - </Button> - </Container> - ); -} diff --git a/src/components/navigation/ErrorBoundary/index.js b/src/components/navigation/ErrorBoundary/index.js deleted file mode 100644 index ee619e282fd231502a52f396257583c0532b6d84..0000000000000000000000000000000000000000 --- a/src/components/navigation/ErrorBoundary/index.js +++ /dev/null @@ -1,4 +0,0 @@ -import { AppErrorBoundary, Break } from "./ErrorBoundary"; - -export { AppErrorBoundary, Break }; -export default AppErrorBoundary; diff --git a/src/components/navigation/NotFound.js b/src/components/navigation/NotFound.js deleted file mode 100644 index 414e8cad44e37ce1dc3b4bc9d5cbe39e0cd95fe9..0000000000000000000000000000000000000000 --- a/src/components/navigation/NotFound.js +++ /dev/null @@ -1,108 +0,0 @@ -import React from "react"; -import { styled } from "@mui/material/styles"; -import { Paper, Typography, Box, Button, Link } from "@mui/material"; -import { RootContainer } from "../../components/common/Container/RootContainer"; -import { useRedirect } from "../../hooks/Redirect"; - -const PREFIX = "NotFound"; - -const classes = { - paper: `${PREFIX}-paper` -}; - -const StyledRootContainer = styled(RootContainer)(({ theme }) => ({ - [`& .${classes.paper}`]: { - maxWidth: "80%", - padding: theme.spacing(4) - } -})); - -export default function NotFound({ message }) { - const redirect = useRedirect(); - - const goHome = () => { - redirect("/home"); - }; - - // if the not found page has recceived any parameters -> show that as content; otherwise show default message - const content = message ? ( - <Box - display="flex" - justifyContent="center" - paddingTop={1} - paddingBottom={3} - > - <Typography variant="h4">{message}</Typography> - </Box> - ) : ( - <> - <Box - display="flex" - justifyContent="center" - paddingTop={1} - paddingBottom={3} - > - <Typography variant="h4">{"Page not found"}</Typography> - </Box> - - <Box - display="flex" - justifyContent="center" - paddingBottom={3} - > - <Typography> - { - "The page you are looking for may have been moved, deleted, or possibly never existed." - } - </Typography> - </Box> - - <Box - display="flex" - justifyContent="center" - paddingBottom={3} - > - <Typography> - Contact the - { - <Link - href="https://jira.esss.lu.se/plugins/servlet/desk/portal/44?requestGroup=137" - target="_blank" - rel="noreferrer" - underline="hover" - > - Service Desk - </Link> - } - if you need support. - </Typography> - </Box> - </> - ); - - return ( - <StyledRootContainer> - <Paper className={classes.paper}> - <Box - display="flex" - justifyContent="center" - > - <Typography variant="h3">404</Typography> - </Box> - {content} - <Box - display="flex" - justifyContent="center" - > - <Button - variant="contained" - color="secondary" - onClick={goHome} - > - Return to Home - </Button> - </Box> - </Paper> - </StyledRootContainer> - ); -} diff --git a/src/components/navigation/NotFoundView/NotFoundView.js b/src/components/navigation/NotFoundView/NotFoundView.js new file mode 100644 index 0000000000000000000000000000000000000000..bc08f94be80578ecc6052f138756a74051b2e847 --- /dev/null +++ b/src/components/navigation/NotFoundView/NotFoundView.js @@ -0,0 +1,26 @@ +/** + * NotFound + * when page not found go (redirect) home ("/") + */ +import React from "react"; +import { string } from "prop-types"; +import { ServerErrorPage, RootPaper } from "@ess-ics/ce-ui-common"; + +const propTypes = { + /** String containing message of page not found. Otherwise defaults to a generic "Page Not Found" message */ + message: string +}; + +export default function NotFoundView({ message }) { + return ( + <RootPaper> + <ServerErrorPage + status={"404"} + message={message} + supportHref={window.SUPPORT_URL} + /> + </RootPaper> + ); +} + +NotFoundView.propTypes = propTypes; diff --git a/src/components/navigation/NotFoundView/index.js b/src/components/navigation/NotFoundView/index.js new file mode 100644 index 0000000000000000000000000000000000000000..b21b7fdb8314d6c5c2079c3e34e7ab0ecdbdc959 --- /dev/null +++ b/src/components/navigation/NotFoundView/index.js @@ -0,0 +1,4 @@ +import NotFoundView from "./NotFoundView"; + +export { NotFoundView }; +export default NotFoundView; diff --git a/src/views/IOC/IOCDetailsContainer.js b/src/views/IOC/IOCDetailsContainer.js index 6c867484779ccdbc7b8b78fe30ea86b37faa2b64..c27adf57554bced11d4b8e6db26ede1f0ec26bfa 100644 --- a/src/views/IOC/IOCDetailsContainer.js +++ b/src/views/IOC/IOCDetailsContainer.js @@ -1,7 +1,7 @@ import React, { useState } from "react"; import { IOCDetailsView } from "./IOCDetailsView"; import { LinearProgress } from "@mui/material"; -import NotFound from "../../components/navigation/NotFound"; +import NotFoundView from "../../components/navigation/NotFoundView/NotFoundView"; import { onFetchEntityError } from "../../components/common/Helper"; import { useIOC } from "../../api/SwaggerApi"; @@ -15,7 +15,7 @@ export function IOCDetailsContainer({ id }) { return ( <> {notFoundError ? ( - <NotFound /> + <NotFoundView /> ) : ioc ? ( <IOCDetailsView ioc={ioc} diff --git a/src/views/TestErrorView.js b/src/views/TestErrorView.js new file mode 100644 index 0000000000000000000000000000000000000000..6cd22c2c2687d5500f6dbf0cc424f53940bbbd70 --- /dev/null +++ b/src/views/TestErrorView.js @@ -0,0 +1,9 @@ +import React, { useEffect } from "react"; + +export const TestErrorView = () => { + useEffect(() => { + throw new Error("Test Error"); + }, []); + + return <div>Test Error Page</div>; +}; diff --git a/src/views/host/HostDetailsContainer.js b/src/views/host/HostDetailsContainer.js index 3be2de7f2dc157af3d2ac0660ace1c99c232c159..38f033c9793d58c07349d61598ad656a3c9aef84 100644 --- a/src/views/host/HostDetailsContainer.js +++ b/src/views/host/HostDetailsContainer.js @@ -3,7 +3,7 @@ import { HostDetailsView } from "./HostDetailsView"; import { LinearProgress } from "@mui/material"; import { useHost } from "../../api/SwaggerApi"; import { onFetchEntityError } from "../../components/common/Helper"; -import NotFound from "../../components/navigation/NotFound"; +import NotFoundView from "../../components/navigation/NotFoundView/NotFoundView"; export function HostDetailsContainer({ id }) { const [notFoundError, setNotFoundError] = useState(); @@ -15,7 +15,7 @@ export function HostDetailsContainer({ id }) { return ( <> {notFoundError ? ( - <NotFound /> + <NotFoundView /> ) : host ? ( <HostDetailsView host={host} diff --git a/src/views/jobs/JobDetailsContainer.js b/src/views/jobs/JobDetailsContainer.js index 263d1317ee0504092e044c63efd3fb698c23e410..cfcb3f7875ce743283d52c768318476f55b747e5 100644 --- a/src/views/jobs/JobDetailsContainer.js +++ b/src/views/jobs/JobDetailsContainer.js @@ -3,7 +3,7 @@ import { JobDetailsView } from "./JobDetailsView"; import { LinearProgress } from "@mui/material"; import { useOperation, useJobById } from "../../api/SwaggerApi"; import { useSafePolling } from "../../hooks/Polling"; -import NotFound from "../../components/navigation/NotFound"; +import NotFoundView from "../../components/navigation/NotFoundView/NotFoundView"; import { onFetchEntityError } from "../../components/common/Helper"; const POLL_DEPLOYMENT_INTERVAL = 5000; @@ -40,7 +40,7 @@ export function JobDetailsContainer({ id }) { return ( <> {notFoundError ? ( - <NotFound /> + <NotFoundView /> ) : operation ? ( <JobDetailsView operation={operation} diff --git a/src/views/records/RecordDetailsView.js b/src/views/records/RecordDetailsView.js index 9d16cb5af4859dd5b41d2fc18d54547491e54cd2..30e23d56c2de7d5e143d61376f2f78934c7d8250 100644 --- a/src/views/records/RecordDetailsView.js +++ b/src/views/records/RecordDetailsView.js @@ -18,7 +18,7 @@ import { applicationTitle, formatDate } from "../../components/common/Helper"; import { useParams } from "react-router-dom"; import { useNavigate } from "react-router-dom"; import { onFetchEntityError } from "../../components/common/Helper"; -import NotFound from "../../components/navigation/NotFound"; +import NotFoundView from "../../components/navigation/NotFoundView/NotFoundView"; export function RecordDetailsView() { const { name } = useParams(); @@ -90,7 +90,7 @@ export function RecordDetailsView() { return ( <RootContainer> {notFoundError ? ( - <NotFound /> + <NotFoundView /> ) : recordLoading ? ( <LinearProgress color="primary" /> ) : (