diff --git a/virtweb_frontend/public/index.html b/virtweb_frontend/public/index.html index aa069f2..ecb1c9e 100644 --- a/virtweb_frontend/public/index.html +++ b/virtweb_frontend/public/index.html @@ -24,7 +24,7 @@ work correctly both with client-side routing and a non-root public URL. Learn how to configure a non-root public URL by running `npm run build`. --> - React App + VirtWeb diff --git a/virtweb_frontend/public/manifest.json b/virtweb_frontend/public/manifest.json index 080d6c7..fb5e09d 100644 --- a/virtweb_frontend/public/manifest.json +++ b/virtweb_frontend/public/manifest.json @@ -1,6 +1,6 @@ { - "short_name": "React App", - "name": "Create React App Sample", + "short_name": "VirtWeb", + "name": "Virtual machines management", "icons": [ { "src": "favicon.ico", diff --git a/virtweb_frontend/src/api/AuthApi.ts b/virtweb_frontend/src/api/AuthApi.ts index 0d117db..d29b9fe 100644 --- a/virtweb_frontend/src/api/AuthApi.ts +++ b/virtweb_frontend/src/api/AuthApi.ts @@ -37,7 +37,6 @@ export class AuthApi { await APIClient.exec({ uri: "/auth/local", method: "POST", - allowFail: true, jsonData: { username: username, password: password, diff --git a/virtweb_frontend/src/routes/auth/LoginRoute.tsx b/virtweb_frontend/src/routes/auth/LoginRoute.tsx index dc9c577..b38d1b9 100644 --- a/virtweb_frontend/src/routes/auth/LoginRoute.tsx +++ b/virtweb_frontend/src/routes/auth/LoginRoute.tsx @@ -1,3 +1,167 @@ -export function LoginRoute() { - return <>; +import { Visibility, VisibilityOff } from "@mui/icons-material"; +import { + Alert, + CircularProgress, + FormControl, + IconButton, + InputAdornment, + InputLabel, + OutlinedInput, + Tooltip, +} from "@mui/material"; +import Box from "@mui/material/Box"; +import Button from "@mui/material/Button"; +import Grid from "@mui/material/Grid"; +import TextField from "@mui/material/TextField"; +import Typography from "@mui/material/Typography"; +import * as React from "react"; +import { Link, useNavigate } from "react-router-dom"; +import { useAuth } from "../../App"; +import { AuthApi } from "../../api/AuthApi"; +import { ServerApi } from "../../api/ServerApi"; + +/** + * Login form + */ +export function LoginRoute(): React.ReactElement { + const [loading, setLoading] = React.useState(false); + const [error, setError] = React.useState(null); + + const auth = useAuth(); + const navigate = useNavigate(); + + const [username, setUsername] = React.useState(""); + const [password, setPassword] = React.useState(""); + + const canSubmit = username.length > 0 && password.length > 0; + + const [showPassword, setShowPassword] = React.useState(false); + const handleClickShowPassword = () => setShowPassword((show) => !show); + + const handleMouseDownPassword = ( + event: React.MouseEvent + ) => { + event.preventDefault(); + }; + + const handleLoginSubmit = async (event: React.FormEvent) => { + event.preventDefault(); + if (!canSubmit) return; + + try { + await AuthApi.LoginWithPassword(username, password); + + navigate("/"); + auth.setSignedIn(true); + } catch (e) { + console.error(e); + setError("Auth failed!"); + } + setLoading(false); + }; + + const authWithOpenID = async () => { + try { + setLoading(true); + + const res = await AuthApi.StartOpenIDLogin(); + window.location.href = res.url; + } catch (e) { + console.error(e); + setError("Failed to initialize OpenID login"); + } + }; + + if (loading) + return ( + <> + + + ); + + return ( + <> + {error && ( + + {error} + + )} + {ServerApi.Config.local_auth_enabled && ( + <> + + Local authentication + + + setUsername(e.target.value)} + autoComplete="username" + autoFocus + /> + + + Password + setPassword(e.target.value)} + autoComplete="current-password" + endAdornment={ + + + + {showPassword ? : } + + + + } + /> + + + + + + )} + +
+ {ServerApi.Config.oidc_auth_enabled && ( + + )} +
+ + ); }