Build base web
This commit is contained in:
92
virtweb_frontend/src/widgets/AsyncWidget.tsx
Normal file
92
virtweb_frontend/src/widgets/AsyncWidget.tsx
Normal file
@ -0,0 +1,92 @@
|
||||
import { Alert, Box, Button, CircularProgress } from "@mui/material";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
|
||||
enum State {
|
||||
Loading,
|
||||
Ready,
|
||||
Error,
|
||||
}
|
||||
|
||||
export function AsyncWidget(p: {
|
||||
loadKey: any;
|
||||
load: () => Promise<void>;
|
||||
errMsg: string;
|
||||
build: () => React.ReactElement;
|
||||
ready?: boolean;
|
||||
errAdditionalElement?: () => React.ReactElement;
|
||||
}): React.ReactElement {
|
||||
const [state, setState] = useState(State.Loading);
|
||||
|
||||
const counter = useRef<any | null>(null);
|
||||
|
||||
const load = async () => {
|
||||
try {
|
||||
setState(State.Loading);
|
||||
await p.load();
|
||||
setState(State.Ready);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
setState(State.Error);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (counter.current === p.loadKey) return;
|
||||
counter.current = p.loadKey;
|
||||
|
||||
load();
|
||||
});
|
||||
|
||||
if (state === State.Error)
|
||||
return (
|
||||
<Box
|
||||
component="div"
|
||||
sx={{
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
height: "100%",
|
||||
flex: "1",
|
||||
flexDirection: "column",
|
||||
backgroundColor: (theme) =>
|
||||
theme.palette.mode === "light"
|
||||
? theme.palette.grey[100]
|
||||
: theme.palette.grey[900],
|
||||
}}
|
||||
>
|
||||
<Alert
|
||||
variant="outlined"
|
||||
severity="error"
|
||||
style={{ margin: "0px 15px 15px 15px" }}
|
||||
>
|
||||
{p.errMsg}
|
||||
</Alert>
|
||||
|
||||
<Button onClick={load}>Try again</Button>
|
||||
|
||||
{p.errAdditionalElement && p.errAdditionalElement()}
|
||||
</Box>
|
||||
);
|
||||
|
||||
if (state === State.Loading || p.ready === false)
|
||||
return (
|
||||
<Box
|
||||
component="div"
|
||||
sx={{
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
height: "100%",
|
||||
flex: "1",
|
||||
backgroundColor: (theme) =>
|
||||
theme.palette.mode === "light"
|
||||
? theme.palette.grey[100]
|
||||
: theme.palette.grey[900],
|
||||
}}
|
||||
>
|
||||
<CircularProgress />
|
||||
</Box>
|
||||
);
|
||||
|
||||
return p.build();
|
||||
}
|
13
virtweb_frontend/src/widgets/AuthSingleMessage.tsx
Normal file
13
virtweb_frontend/src/widgets/AuthSingleMessage.tsx
Normal file
@ -0,0 +1,13 @@
|
||||
import { Button } from "@mui/material";
|
||||
import { Link } from "react-router-dom";
|
||||
|
||||
export function AuthSingleMessage(p: { message: string }): React.ReactElement {
|
||||
return (
|
||||
<>
|
||||
<p style={{ textAlign: "center" }}>{p.message}</p>
|
||||
<Link to={"/"}>
|
||||
<Button>Retour à l'accueil</Button>
|
||||
</Link>
|
||||
</>
|
||||
);
|
||||
}
|
3
virtweb_frontend/src/widgets/BaseAuthenticatedPage.tsx
Normal file
3
virtweb_frontend/src/widgets/BaseAuthenticatedPage.tsx
Normal file
@ -0,0 +1,3 @@
|
||||
export function BaseAuthenticatedPage(): React.ReactElement {
|
||||
return <>ready with login</>;
|
||||
}
|
90
virtweb_frontend/src/widgets/BaseLoginPage.tsx
Normal file
90
virtweb_frontend/src/widgets/BaseLoginPage.tsx
Normal file
@ -0,0 +1,90 @@
|
||||
import { mdiServer } from "@mdi/js";
|
||||
import Icon from "@mdi/react";
|
||||
import Avatar from "@mui/material/Avatar";
|
||||
import Box from "@mui/material/Box";
|
||||
import CssBaseline from "@mui/material/CssBaseline";
|
||||
import Grid from "@mui/material/Grid";
|
||||
import Paper from "@mui/material/Paper";
|
||||
import Typography from "@mui/material/Typography";
|
||||
import { Link, Outlet } from "react-router-dom";
|
||||
|
||||
function Copyright(props: any) {
|
||||
return (
|
||||
<Typography
|
||||
variant="body2"
|
||||
color="text.secondary"
|
||||
align="center"
|
||||
style={{ marginTop: "20px" }}
|
||||
{...props}
|
||||
>
|
||||
{"Copyright © "}
|
||||
<a
|
||||
color="inherit"
|
||||
href="https://0ph.fr/"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
style={{ color: "inherit" }}
|
||||
>
|
||||
Pierre HUBERT
|
||||
</a>{" "}
|
||||
{new Date().getFullYear()}
|
||||
{"."}
|
||||
</Typography>
|
||||
);
|
||||
}
|
||||
|
||||
export function BaseLoginPage() {
|
||||
return (
|
||||
<Grid container component="main" sx={{ height: "100vh" }}>
|
||||
<CssBaseline />
|
||||
<Grid
|
||||
item
|
||||
xs={false}
|
||||
sm={4}
|
||||
md={7}
|
||||
sx={{
|
||||
backgroundImage: "url(/login_splash.jpg)",
|
||||
backgroundRepeat: "no-repeat",
|
||||
backgroundColor: (t) =>
|
||||
t.palette.mode === "light"
|
||||
? t.palette.grey[50]
|
||||
: t.palette.grey[900],
|
||||
backgroundSize: "cover",
|
||||
backgroundPosition: "center",
|
||||
}}
|
||||
/>
|
||||
<Grid item xs={12} sm={8} md={5} component={Paper} elevation={6} square>
|
||||
<Box
|
||||
sx={{
|
||||
my: 8,
|
||||
mx: 4,
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
<Avatar sx={{ m: 1, bgcolor: "secondary.main" }}>
|
||||
<Icon path={mdiServer} size={1} />
|
||||
</Avatar>
|
||||
<Link to="/" style={{ color: "inherit", textDecoration: "none" }}>
|
||||
<Typography component="h1" variant="h5">
|
||||
VirtWeb
|
||||
</Typography>
|
||||
</Link>
|
||||
<Typography
|
||||
component="h1"
|
||||
variant="h6"
|
||||
style={{ margin: "10px 0px 30px 0px" }}
|
||||
>
|
||||
Virtual Machines Management
|
||||
</Typography>
|
||||
|
||||
{/* inner page */}
|
||||
<Outlet />
|
||||
|
||||
<Copyright sx={{ mt: 5 }} />
|
||||
</Box>
|
||||
</Grid>
|
||||
</Grid>
|
||||
);
|
||||
}
|
18
virtweb_frontend/src/widgets/LoadServerConfig.tsx
Normal file
18
virtweb_frontend/src/widgets/LoadServerConfig.tsx
Normal file
@ -0,0 +1,18 @@
|
||||
import { PropsWithChildren } from "react";
|
||||
import { AsyncWidget } from "./AsyncWidget";
|
||||
import { ServerApi } from "../api/ServerApi";
|
||||
|
||||
export function LoadServerConfig(p: PropsWithChildren): React.ReactElement {
|
||||
const load = async () => {
|
||||
await ServerApi.LoadConfig();
|
||||
};
|
||||
|
||||
return (
|
||||
<AsyncWidget
|
||||
loadKey={0}
|
||||
errMsg="Failed to load server config!"
|
||||
load={load}
|
||||
build={() => <>{p.children}</>}
|
||||
/>
|
||||
);
|
||||
}
|
16
virtweb_frontend/src/widgets/RouterLink.tsx
Normal file
16
virtweb_frontend/src/widgets/RouterLink.tsx
Normal file
@ -0,0 +1,16 @@
|
||||
import { PropsWithChildren } from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
|
||||
export function RouterLink(
|
||||
p: PropsWithChildren<{ to: string; target?: React.HTMLAttributeAnchorTarget }>
|
||||
): React.ReactElement {
|
||||
return (
|
||||
<Link
|
||||
to={p.to}
|
||||
target={p.target}
|
||||
style={{ color: "inherit", textDecoration: "inherit" }}
|
||||
>
|
||||
{p.children}
|
||||
</Link>
|
||||
);
|
||||
}
|
Reference in New Issue
Block a user