mirror of
https://github.com/pomerium/pomerium.git
synced 2025-07-17 00:36:09 +02:00
proxy: add routes HTML page (#5443)
* proxy: add route portal json * fix 405 issue * proxy: add routes HTML page
This commit is contained in:
parent
e9786f9719
commit
97ba21b95a
11 changed files with 269 additions and 44 deletions
|
@ -68,6 +68,11 @@ const Header: FC<HeaderProps> = ({ includeSidebar, data }) => {
|
|||
setDrawerOpen(false);
|
||||
};
|
||||
|
||||
const handleRoutes = (evt: React.MouseEvent): void => {
|
||||
evt.preventDefault();
|
||||
window.open("/.pomerium/routes");
|
||||
};
|
||||
|
||||
const handleUserInfo = (evt: React.MouseEvent): void => {
|
||||
evt.preventDefault();
|
||||
window.open("/.pomerium/");
|
||||
|
@ -145,6 +150,7 @@ const Header: FC<HeaderProps> = ({ includeSidebar, data }) => {
|
|||
anchorEl={anchorEl}
|
||||
>
|
||||
<MenuItem onClick={handleUserInfo}>User Info</MenuItem>
|
||||
<MenuItem onClick={handleRoutes}>Routes</MenuItem>
|
||||
<MenuItem onClick={handleLogout}>Logout</MenuItem>
|
||||
</Menu>
|
||||
</Toolbar>
|
||||
|
|
145
ui/src/components/RoutesPage.tsx
Normal file
145
ui/src/components/RoutesPage.tsx
Normal file
|
@ -0,0 +1,145 @@
|
|||
import {
|
||||
Avatar,
|
||||
Box,
|
||||
Card,
|
||||
CardActionArea,
|
||||
CardContent,
|
||||
CardHeader,
|
||||
Grid,
|
||||
IconButton,
|
||||
Paper,
|
||||
Snackbar,
|
||||
Stack,
|
||||
Typography,
|
||||
} from "@mui/material";
|
||||
import React, { FC, useState } from "react";
|
||||
import { Clipboard, Link } from "react-feather";
|
||||
|
||||
import { Route, RoutesPageData } from "../types";
|
||||
import Section from "./Section";
|
||||
import SidebarPage from "./SidebarPage";
|
||||
|
||||
type RouteCardProps = {
|
||||
route: Route;
|
||||
};
|
||||
const RouteCard: FC<RouteCardProps> = ({ route }) => {
|
||||
const [showSnackbar, setShowSnackbar] = useState(false);
|
||||
|
||||
const handleClick = (evt: React.MouseEvent) => {
|
||||
if (route.connect_command) {
|
||||
evt.preventDefault();
|
||||
navigator.clipboard.writeText(route.connect_command);
|
||||
setShowSnackbar(true);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Card raised={true}>
|
||||
<CardActionArea href={route.from} target="_blank" onClick={handleClick}>
|
||||
<CardHeader
|
||||
avatar={
|
||||
route.logo_url ? (
|
||||
<Avatar src={route.logo_url} />
|
||||
) : route.type === "tcp" ? (
|
||||
<Avatar>TCP</Avatar>
|
||||
) : route.type === "udp" ? (
|
||||
<Avatar>UDP</Avatar>
|
||||
) : (
|
||||
<Avatar>
|
||||
<Link />
|
||||
</Avatar>
|
||||
)
|
||||
}
|
||||
action={
|
||||
route.connect_command && (
|
||||
<IconButton title="Copy Command">
|
||||
<Clipboard />
|
||||
</IconButton>
|
||||
)
|
||||
}
|
||||
title={route.name}
|
||||
/>
|
||||
<CardContent>
|
||||
{route.description && (
|
||||
<Typography variant="body2">{route.description}</Typography>
|
||||
)}
|
||||
{route.connect_command && (
|
||||
<Box
|
||||
component="span"
|
||||
sx={{ fontFamily: '"DM Mono"', fontSize: "12px" }}
|
||||
>
|
||||
{route.connect_command}
|
||||
</Box>
|
||||
)}
|
||||
</CardContent>
|
||||
</CardActionArea>
|
||||
<Snackbar
|
||||
anchorOrigin={{ vertical: "bottom", horizontal: "right" }}
|
||||
open={showSnackbar}
|
||||
autoHideDuration={3000}
|
||||
onClose={() => setShowSnackbar(false)}
|
||||
message="Copied to Clipboard"
|
||||
/>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
type RoutesSectionProps = {
|
||||
type: "http" | "tcp" | "udp";
|
||||
title: string;
|
||||
allRoutes: Route[];
|
||||
};
|
||||
const RoutesSection: FC<RoutesSectionProps> = ({ type, title, allRoutes }) => {
|
||||
const routes = allRoutes?.filter((r) => r.type === type);
|
||||
if (routes?.length === 0) {
|
||||
return <></>;
|
||||
}
|
||||
|
||||
return (
|
||||
<Section title={title}>
|
||||
<Grid container spacing={2} justifyContent="center">
|
||||
{routes?.map((r) => (
|
||||
<Grid key={r.id} item sx={{ width: 300 }}>
|
||||
<RouteCard route={r} />
|
||||
</Grid>
|
||||
))}
|
||||
</Grid>
|
||||
</Section>
|
||||
);
|
||||
};
|
||||
|
||||
type RoutesPageProps = {
|
||||
data: RoutesPageData;
|
||||
};
|
||||
const RoutesPage: FC<RoutesPageProps> = ({ data }) => {
|
||||
return (
|
||||
<SidebarPage>
|
||||
<Stack spacing={2}>
|
||||
{data?.routes ? (
|
||||
<>
|
||||
<RoutesSection
|
||||
type={"http"}
|
||||
title={"HTTP Routes"}
|
||||
allRoutes={data.routes}
|
||||
/>
|
||||
<RoutesSection
|
||||
type={"tcp"}
|
||||
title={"TCP Routes"}
|
||||
allRoutes={data.routes}
|
||||
/>
|
||||
<RoutesSection
|
||||
type={"udp"}
|
||||
title={"UDP Routes"}
|
||||
allRoutes={data.routes}
|
||||
/>
|
||||
</>
|
||||
) : (
|
||||
<Paper sx={{ padding: 3 }}>
|
||||
<Typography>No accessible routes found</Typography>
|
||||
</Paper>
|
||||
)}
|
||||
</Stack>
|
||||
</SidebarPage>
|
||||
);
|
||||
};
|
||||
export default RoutesPage;
|
44
ui/src/components/SidebarPage.tsx
Normal file
44
ui/src/components/SidebarPage.tsx
Normal file
|
@ -0,0 +1,44 @@
|
|||
import { Box, Container, Drawer, useMediaQuery, useTheme } from "@mui/material";
|
||||
import React, { FC } from "react";
|
||||
|
||||
import { ToolbarOffset } from "./ToolbarOffset";
|
||||
import UserSidebarContent from "./UserSidebarContent";
|
||||
|
||||
const SidebarPage: FC = ({ children }) => {
|
||||
const theme = useTheme();
|
||||
const mdUp = useMediaQuery(() => theme.breakpoints.up("md"), {
|
||||
defaultMatches: true,
|
||||
noSsr: false,
|
||||
});
|
||||
|
||||
return (
|
||||
<Container maxWidth={false}>
|
||||
{mdUp && (
|
||||
<Drawer
|
||||
anchor="left"
|
||||
open
|
||||
PaperProps={{
|
||||
sx: {
|
||||
backgroundColor: "neutral.900",
|
||||
width: 256,
|
||||
height: "100vh",
|
||||
},
|
||||
}}
|
||||
variant="persistent"
|
||||
>
|
||||
<ToolbarOffset />
|
||||
<UserSidebarContent close={null} />
|
||||
<ToolbarOffset />
|
||||
</Drawer>
|
||||
)}
|
||||
<Box
|
||||
sx={{
|
||||
marginLeft: mdUp ? "256px" : "0px",
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</Box>
|
||||
</Container>
|
||||
);
|
||||
};
|
||||
export default SidebarPage;
|
|
@ -1,15 +1,11 @@
|
|||
import {
|
||||
Button,
|
||||
Container,
|
||||
Dialog,
|
||||
DialogActions,
|
||||
DialogContent,
|
||||
DialogContentText,
|
||||
DialogTitle,
|
||||
Drawer,
|
||||
Stack,
|
||||
useMediaQuery,
|
||||
useTheme,
|
||||
} from "@mui/material";
|
||||
import React, { FC, useContext, useEffect, useState } from "react";
|
||||
|
||||
|
@ -18,18 +14,12 @@ import { UserInfoData } from "../types";
|
|||
import GroupDetails from "./GroupDetails";
|
||||
import SessionDetails from "./SessionDetails";
|
||||
import SessionDeviceCredentials from "./SessionDeviceCredentials";
|
||||
import { ToolbarOffset } from "./ToolbarOffset";
|
||||
import { UserSidebarContent } from "./UserSidebarContent";
|
||||
import SidebarPage from "./SidebarPage";
|
||||
|
||||
type UserInfoPageProps = {
|
||||
data: UserInfoData & { page: "DeviceEnrolled" | "UserInfo" };
|
||||
};
|
||||
const UserInfoPage: FC<UserInfoPageProps> = ({ data }) => {
|
||||
const theme = useTheme();
|
||||
const mdUp = useMediaQuery(() => theme.breakpoints.up("md"), {
|
||||
defaultMatches: true,
|
||||
noSsr: false,
|
||||
});
|
||||
const { subpage } = useContext(SubpageContext);
|
||||
|
||||
const [showDeviceEnrolled, setShowDeviceEnrolled] = useState(false);
|
||||
|
@ -47,7 +37,7 @@ const UserInfoPage: FC<UserInfoPageProps> = ({ data }) => {
|
|||
}
|
||||
|
||||
return (
|
||||
<Container maxWidth={false}>
|
||||
<SidebarPage>
|
||||
<Dialog open={showDeviceEnrolled} onClose={handleCloseDeviceEnrolled}>
|
||||
<DialogTitle>Device Enrolled</DialogTitle>
|
||||
<DialogContent>
|
||||
|
@ -57,30 +47,7 @@ const UserInfoPage: FC<UserInfoPageProps> = ({ data }) => {
|
|||
<Button onClick={handleCloseDeviceEnrolled}>OK</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
{mdUp && (
|
||||
<Drawer
|
||||
anchor="left"
|
||||
open
|
||||
PaperProps={{
|
||||
sx: {
|
||||
backgroundColor: "neutral.900",
|
||||
width: 256,
|
||||
height: "100vh",
|
||||
},
|
||||
}}
|
||||
variant="persistent"
|
||||
>
|
||||
<ToolbarOffset />
|
||||
<UserSidebarContent close={null} />
|
||||
<ToolbarOffset />
|
||||
</Drawer>
|
||||
)}
|
||||
<Stack
|
||||
spacing={3}
|
||||
sx={{
|
||||
marginLeft: mdUp ? "256px" : "0px",
|
||||
}}
|
||||
>
|
||||
<Stack spacing={3}>
|
||||
{subpage === "User" && (
|
||||
<SessionDetails session={data?.session} profile={data?.profile} />
|
||||
)}
|
||||
|
@ -103,7 +70,7 @@ const UserInfoPage: FC<UserInfoPageProps> = ({ data }) => {
|
|||
/>
|
||||
)}
|
||||
</Stack>
|
||||
</Container>
|
||||
</SidebarPage>
|
||||
);
|
||||
};
|
||||
export default UserInfoPage;
|
||||
|
|
|
@ -6,27 +6,36 @@ import {
|
|||
ListItemText,
|
||||
} from "@mui/material";
|
||||
import React, { FC, ReactNode, useContext } from "react";
|
||||
import { User, Users } from "react-feather";
|
||||
import { Link, User, Users } from "react-feather";
|
||||
|
||||
import { SubpageContext } from "../context/Subpage";
|
||||
|
||||
export interface Subpage {
|
||||
icon: ReactNode;
|
||||
title: string;
|
||||
pathname: string;
|
||||
}
|
||||
|
||||
export const sectionList: Subpage[] = [
|
||||
{
|
||||
title: "User",
|
||||
icon: <User />,
|
||||
pathname: "/.pomerium/",
|
||||
},
|
||||
{
|
||||
title: "Groups Info",
|
||||
icon: <Users />,
|
||||
pathname: "/.pomerium/",
|
||||
},
|
||||
{
|
||||
title: "Devices Info",
|
||||
icon: <Devices />,
|
||||
pathname: "/.pomerium/",
|
||||
},
|
||||
{
|
||||
title: "Routes",
|
||||
icon: <Link />,
|
||||
pathname: "/.pomerium/routes",
|
||||
},
|
||||
];
|
||||
type UserSidebarContent = {
|
||||
|
@ -39,12 +48,17 @@ export const UserSidebarContent: FC<UserSidebarContent> = ({
|
|||
|
||||
return (
|
||||
<List>
|
||||
{sectionList.map(({ title, icon }) => {
|
||||
{sectionList.map(({ title, icon, pathname }) => {
|
||||
return (
|
||||
<ListItemButton
|
||||
key={"tab " + title}
|
||||
selected={title === info.subpage}
|
||||
onClick={() => {
|
||||
if (location.pathname !== pathname) {
|
||||
location.href =
|
||||
pathname + "#subpage=" + encodeURIComponent(title);
|
||||
return;
|
||||
}
|
||||
info.setSubpage(title);
|
||||
!!close && close();
|
||||
}}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue