Skip to content

Commit

Permalink
feat: use react-router to enable linking to webUI pages (#522)
Browse files Browse the repository at this point in the history
  • Loading branch information
garethgeorge authored Oct 20, 2024
1 parent afcecae commit fff3dbd
Show file tree
Hide file tree
Showing 7 changed files with 194 additions and 122 deletions.
40 changes: 40 additions & 0 deletions webui/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions webui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-js-cron": "^5.0.1",
"react-router": "^6.27.0",
"react-router-dom": "^6.27.0",
"react-virtualized": "^9.22.5",
"recharts": "^2.12.7",
"typescript": "^5.2.2"
Expand Down
3 changes: 3 additions & 0 deletions webui/src/components/OperationTree.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -393,6 +393,9 @@ const BackupViewContainer = ({ children }: { children: React.ReactNode }) => {

// handle scroll events to keep the fixed container in view.
const handleScroll = () => {
if (!ref.current) {
return;
}
const refRect = ref.current!.getBoundingClientRect();
let wiggle = Math.max(refRect.height - window.innerHeight, 0);
let topY = Math.max(ref.current!.getBoundingClientRect().top, 0);
Expand Down
16 changes: 9 additions & 7 deletions webui/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,13 @@ import { ModalContextProvider } from "./components/ModalManager";
import "react-js-cron/dist/styles.css";
import { ConfigProvider as AntdConfigProvider, theme } from "antd";
import { ConfigContextProvider } from "./components/ConfigProvider";
import { MainContentProvider } from "./views/MainContentArea";
import { HashRouter } from "react-router-dom";

const Root = ({ children }: { children: React.ReactNode }) => {
return (
<ConfigContextProvider>
<AlertContextProvider>
<MainContentProvider>
<ModalContextProvider>{children}</ModalContextProvider>
</MainContentProvider>
<ModalContextProvider>{children}</ModalContextProvider>
</AlertContextProvider>
</ConfigContextProvider>
);
Expand All @@ -34,8 +32,12 @@ el &&
],
}}
>
<Root>
<App />
</Root>
<React.StrictMode>
<Root>
<HashRouter>
<App />
</HashRouter>
</Root>
</React.StrictMode>
</AntdConfigProvider>
);
173 changes: 128 additions & 45 deletions webui/src/views/App.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useEffect, useState } from "react";
import React, { Suspense, useEffect, useState } from "react";
import {
ScheduleOutlined,
DatabaseOutlined,
Expand All @@ -9,7 +9,7 @@ import {
LoadingOutlined,
} from "@ant-design/icons";
import type { MenuProps } from "antd";
import { Button, Layout, Menu, Spin, theme } from "antd";
import { Button, Empty, Layout, Menu, Spin, theme } from "antd";
import { Config } from "../../gen/ts/v1/config_pb";
import { useAlertApi } from "../components/Alerts";
import { useShowModal } from "../components/ModalManager";
Expand All @@ -25,28 +25,104 @@ import _ from "lodash";
import { Code } from "@connectrpc/connect";
import { LoginModal } from "./LoginModal";
import { backrestService, setAuthToken } from "../api";
import { MainContentArea, useSetContent } from "./MainContentArea";
import { GettingStartedGuide } from "./GettingStartedGuide";
import { useConfig } from "../components/ConfigProvider";
import { shouldShowSettings } from "../state/configutil";
import { OpSelector } from "../../gen/ts/v1/service_pb";
import { colorForStatus } from "../state/flowdisplayaggregator";
import { getStatusForSelector } from "../state/logstate";
import {
createHashRouter,
Route,
RouterProvider,
Routes,
useNavigate,
useParams,
} from "react-router-dom";
import { MainContentAreaTemplate } from "./MainContentArea";

const { Header, Sider } = Layout;

const SummaryDashboard = React.lazy(() =>
import("./SummaryDashboard").then((m) => ({
default: m.SummaryDashboard,
}))
);

const GettingStartedGuide = React.lazy(() =>
import("./GettingStartedGuide").then((m) => ({
default: m.GettingStartedGuide,
}))
);

const PlanView = React.lazy(() =>
import("./PlanView").then((m) => ({
default: m.PlanView,
}))
);

const RepoView = React.lazy(() =>
import("./RepoView").then((m) => ({
default: m.RepoView,
}))
);

const RepoViewContainer = () => {
const { repoId } = useParams();
const [config, setConfig] = useConfig();

if (!config) {
return <Spin />;
}

const repo = config.repos.find((r) => r.id === repoId);

return (
<MainContentAreaTemplate
breadcrumbs={[{ title: "Repo" }, { title: repoId! }]}
key={repoId}
>
{repo ? (
<RepoView repo={repo} />
) : (
<Empty description={`Repo ${repoId} not found`} />
)}
</MainContentAreaTemplate>
);
};

const PlanViewContainer = () => {
const { planId } = useParams();
const [config, setConfig] = useConfig();

if (!config) {
return <Spin />;
}

const plan = config.plans.find((p) => p.id === planId);
return (
<MainContentAreaTemplate
breadcrumbs={[{ title: "Plan" }, { title: planId! }]}
key={planId}
>
{plan ? (
<PlanView plan={plan} />
) : (
<Empty description={`Plan ${planId} not found`} />
)}
</MainContentAreaTemplate>
);
};

export const App: React.FC = () => {
const {
token: { colorBgContainer, colorTextLightSolid },
} = theme.useToken();
const alertApi = useAlertApi()!;
const showModal = useShowModal();
const setContent = useSetContent();
const navigate = useNavigate();
const [config, setConfig] = useConfig();

useEffect(() => {
showModal(<Spin spinning={true} fullscreen />);

backrestService
.getConfig({})
.then((config) => {
Expand Down Expand Up @@ -85,30 +161,12 @@ export const App: React.FC = () => {
});
}, []);

const showSummaryDashboard = async () => {
const { SummaryDashboard } = await import("./SummaryDashboard");
setContent(
<React.Suspense fallback={<Spin />}>
<SummaryDashboard />
</React.Suspense>,
[
{
title: "Summary Dashboard",
},
]
);
};

useEffect(() => {
if (config === null) {
setContent(<p>Loading...</p>, []);
} else {
showSummaryDashboard();
}
}, [config === null]);

const items = getSidenavItems(config);

if (!config) {
return <Spin />;
}

return (
<Layout style={{ height: "auto", minHeight: "100vh" }}>
<Header
Expand All @@ -122,7 +180,9 @@ export const App: React.FC = () => {
>
<a
style={{ color: colorTextLightSolid }}
onClick={showSummaryDashboard}
onClick={() => {
navigate("/");
}}
>
<img
src={LogoSvg}
Expand Down Expand Up @@ -176,17 +236,50 @@ export const App: React.FC = () => {
items={items}
/>
</Sider>
<MainContentArea />
<Suspense fallback={<Spin />}>
<Routes>
<Route
path="/"
element={
<MainContentAreaTemplate breadcrumbs={[{ title: "Summary" }]}>
<SummaryDashboard />
</MainContentAreaTemplate>
}
/>
<Route
path="/getting-started"
element={
<MainContentAreaTemplate
breadcrumbs={[{ title: "Getting Started" }]}
>
<GettingStartedGuide />
</MainContentAreaTemplate>
}
/>
<Route path="/plan/:planId" element={<PlanViewContainer />} />
<Route path="/repo/:repoId" element={<RepoViewContainer />} />
<Route
path="/*"
element={
<MainContentAreaTemplate breadcrumbs={[]}>
<Empty description="Page not found" />
</MainContentAreaTemplate>
}
/>
</Routes>
</Suspense>
</Layout>
</Layout>
);
};

const getSidenavItems = (config: Config | null): MenuProps["items"] => {
const showModal = useShowModal();
const setContent = useSetContent();
const navigate = useNavigate();

if (!config) return [];
if (!config) {
return;
}

const configPlans = config.plans || [];
const configRepos = config.repos || [];
Expand Down Expand Up @@ -226,12 +319,7 @@ const getSidenavItems = (config: Config | null): MenuProps["items"] => {
</div>
),
onClick: async () => {
const { PlanView } = await import("./PlanView");

setContent(<PlanView key={plan.id} plan={plan} />, [
{ title: "Plans" },
{ title: plan.id || "" },
]);
navigate(`/plan/${plan.id}`);
},
};
}),
Expand Down Expand Up @@ -272,12 +360,7 @@ const getSidenavItems = (config: Config | null): MenuProps["items"] => {
</div>
),
onClick: async () => {
const { RepoView } = await import("./RepoView");

setContent(<RepoView key={repo.id} repo={repo} />, [
{ title: "Repos" },
{ title: repo.id || "" },
]);
navigate(`/repo/${repo.id}`);
},
};
}),
Expand Down
Loading

0 comments on commit fff3dbd

Please sign in to comment.