diff --git a/packages/web_ui/src/components/ErrorBoundary.jsx b/packages/web_ui/src/components/ErrorBoundary.jsx
new file mode 100644
index 00000000..4fed84e1
--- /dev/null
+++ b/packages/web_ui/src/components/ErrorBoundary.jsx
@@ -0,0 +1,20 @@
+import React from "react";
+
+
+export default class ErrorBoundary extends React.Component {
+ constructor(props) {
+ super(props);
+ this.state = { error: null };
+ }
+
+ static getDerivedStateFromError(error) {
+ return { error };
+ }
+
+ render() {
+ if (this.state.error) {
+ return ;
+ }
+ return this.props.children;
+ }
+}
diff --git a/packages/web_ui/src/components/ErrorPage.jsx b/packages/web_ui/src/components/ErrorPage.jsx
new file mode 100644
index 00000000..855bfce7
--- /dev/null
+++ b/packages/web_ui/src/components/ErrorPage.jsx
@@ -0,0 +1,16 @@
+import React, { useContext } from "react";
+import { Typography } from "antd";
+
+import libLink from "@clusterio/lib/link";
+
+import PageLayout from "./PageLayout";
+
+const { Paragraph } = Typography;
+
+
+export default function ErrorPage(props) {
+ return
+ An unexpected error occured
+ {props.error.stack}
+ ;
+};
diff --git a/packages/web_ui/src/components/SiteLayout.jsx b/packages/web_ui/src/components/SiteLayout.jsx
index d82ef9e5..751a56e4 100644
--- a/packages/web_ui/src/components/SiteLayout.jsx
+++ b/packages/web_ui/src/components/SiteLayout.jsx
@@ -3,6 +3,9 @@ import { Switch, Route, useHistory } from "react-router-dom";
import { Layout, Menu } from "antd";
import webUiPackage from "../../package.json";
+import ErrorBoundary from "./ErrorBoundary";
+import ErrorPage from "./ErrorPage";
+
const { Header, Sider } = Layout;
@@ -74,7 +77,9 @@ export default function SiteLayout(props) {
)}
{pages.map(page =>
- {page.content}
+
+ {page.content}
+
)}
diff --git a/packages/web_ui/src/index.css b/packages/web_ui/src/index.css
index ba26fe24..dda791ca 100644
--- a/packages/web_ui/src/index.css
+++ b/packages/web_ui/src/index.css
@@ -82,6 +82,14 @@ body {
background-size: 64px;
}
+.error-traceback code {
+ display: block;
+ margin: 0;
+ background: #000;
+ border-color: #252525;
+ white-space: pre-wrap;
+}
+
.rcon-result code {
display: block;
background: #000;