Make web pages pluggable

Refactor the page handling of the web interface to allow plugins to add
their own pages.
This commit is contained in:
Hornwitser committed 2021-03-20 03:35:05 +01:00
1 parent 73c713462c
commit 3d26371fa9
3 files changed
+67 -41

No files matched your search

+22
View File
@@ -439,6 +439,21 @@ class BaseControlPlugin {
* form.
*/
/**
* Plugin supplied pages
* @typedef {Object} module:Lib/plugin~Page
* @property {string} path - URL path to this page.
* @property {string=} sidebarPath -
* If present and this path matches one of the pages in the sidebar it
* will cause that sidebar entry to be highlighted as active.
* @property {string=} sidebarName -
* If present creates an entry in the sidebar for this page with the
* given text.
* @property {ReactNode} content -
* A react node which is rendered when this page is navigated to.
* Should render a PageLayout.
*/
/**
* Base class for web interface plugins
*
@@ -477,6 +492,13 @@ class BaseWebPlugin {
* @type {Array<module:lib/plugin~LoginForm>}
*/
this.loginForms = [];
/**
* List of pages provided by this plugin
*
* @type {Array<module:lib/plugin~Page>}
*/
this.pages = [];
}
/**
+16 -41
View File
@@ -1,4 +1,4 @@
import React, { useEffect, useState } from "react";
import React, { useContext, useEffect, useState } from "react";
import { Switch, Route, useHistory } from "react-router-dom";
import { Dropdown, Layout, Menu } from "antd";
import UserOutlined from "@ant-design/icons/UserOutlined";
@@ -7,46 +7,21 @@ import webUiPackage from "../../package.json";
import { useAccount } from "../model/account";
import ErrorBoundary from "./ErrorBoundary";
import ErrorPage from "./ErrorPage";
import PluginsContext from "./PluginsContext";
import { pages } from "../pages";
const { Header, Sider } = Layout;
import MasterPage from "./MasterPage";
import SlavesPage from "./SlavesPage";
import InstancesPage from "./InstancesPage";
import InstanceViewPage from "./InstanceViewPage";
import UsersPage from "./UsersPage";
import UserViewPage from "./UserViewPage";
import RolesPage from "./RolesPage";
import RoleViewPage from "./RoleViewPage";
import PluginsPage from "./PluginsPage";
import PluginViewPage from "./PluginViewPage";
const pages = [
{ path: "/instances/:id/view", sidebarPath: "/instances", content: <InstanceViewPage /> },
{ path: "/users/:name/view", sidebarPath: "/users", content: <UserViewPage /> },
{ path: "/roles/:id/view", sidebarPath: "/roles", content: <RoleViewPage /> },
{ path: "/plugins/:name/view", sidebarPath: "/plugins", content: <PluginViewPage /> },
];
const sidebar = [
{ name: "Master", path: "/master", content: <MasterPage /> },
{ name: "Slaves", path: "/slaves", content: <SlavesPage /> },
{ name: "Instances", path: "/instances", content: <InstancesPage />},
{ name: "Users", path: "/users", content: <UsersPage /> },
{ name: "Roles", path: "/roles", content: <RolesPage /> },
{ name: "Plugins", path: "/plugins", content: <PluginsPage /> },
];
export default function SiteLayout(props) {
let history = useHistory();
let [sidebarPath, setSidebarPath] = useState(null);
let [currentSidebarPath, setCurrentSidebarPath] = useState(null);
let account = useAccount();
let plugins = useContext(PluginsContext);
function SetSidebar(setSidebarProps) {
useEffect(() => {
setSidebarPath(setSidebarProps.path);
setCurrentSidebarPath(setSidebarProps.path);
});
return null;
}
@@ -57,6 +32,10 @@ export default function SiteLayout(props) {
<Menu.Item danger onClick={() => { account.logOut(); }}>Log out</Menu.Item>
</Menu>;
let combinedPages = [...pages];
for (let plugin of plugins.values()) {
combinedPages.push(...plugin.pages);
}
return <Layout style={{ minHeight: "100vh" }}>
<Header className="header">
@@ -86,27 +65,23 @@ export default function SiteLayout(props) {
>
<Menu
mode="inline"
selectedKeys={[sidebarPath]}
selectedKeys={[currentSidebarPath]}
style={{ height: "100%", borderRight: 0 }}
onClick={({ key }) => history.push(key)}
>
{sidebar.map(({name, path}) => <Menu.Item key={path}>{name}</Menu.Item>)}
{combinedPages.map(({sidebarName, path}) => (
sidebarName ? <Menu.Item key={path}>{sidebarName}</Menu.Item> : null)
)}
</Menu>
</Sider>
<Layout className="site-layout-content-container">
<Switch>
{sidebar.map(({path, content}) => <Route exact path={path} key={path}>
<SetSidebar path={path} />
{combinedPages.map(({path, sidebarPath, content}) => <Route exact path={path} key={path}>
<SetSidebar path={sidebarPath ? sidebarPath : path} />
<ErrorBoundary Component={ErrorPage}>
{content}
</ErrorBoundary>
</Route>)}
{pages.map(page => <Route exact path={page.path} key={page.path}>
<SetSidebar path={page.sidebarPath} />
<ErrorBoundary Component={ErrorPage}>
{page.content}
</ErrorBoundary>
</Route>)}
<Route>
<SetSidebar path={null} />
</Route>
+29
View File
@@ -0,0 +1,29 @@
import React from "react";
import MasterPage from "./components/MasterPage";
import SlavesPage from "./components/SlavesPage";
import InstancesPage from "./components/InstancesPage";
import InstanceViewPage from "./components/InstanceViewPage";
import UsersPage from "./components/UsersPage";
import UserViewPage from "./components/UserViewPage";
import RolesPage from "./components/RolesPage";
import RoleViewPage from "./components/RoleViewPage";
import PluginsPage from "./components/PluginsPage";
import PluginViewPage from "./components/PluginViewPage";
export const pages = [
{ path: "/master", sidebarName: "Master", content: <MasterPage /> },
{ path: "/slaves", sidebarName: "Slaves", content: <SlavesPage /> },
{ path: "/instances", sidebarName: "Instances", content: <InstancesPage />},
{ path: "/instances/:id/view", sidebarPath: "/instances", content: <InstanceViewPage /> },
{ path: "/users", sidebarName: "Users", content: <UsersPage /> },
{ path: "/users/:name/view", sidebarPath: "/users", content: <UserViewPage /> },
{ path: "/roles", sidebarName: "Roles", content: <RolesPage /> },
{ path: "/roles/:id/view", sidebarPath: "/roles", content: <RoleViewPage /> },
{ path: "/plugins", sidebarName: "Plugins", content: <PluginsPage /> },
{ path: "/plugins/:name/view", sidebarPath: "/plugins", content: <PluginViewPage /> },
];
export const sidebarPages = [
];