mirror of
https://github.com/UA-Fediland/synapse-admin.git
synced 2024-11-25 15:55:22 +00:00
Add login page
Change-Id: I167f6492aa2608558bf2ac4e6604dd584de6db66
This commit is contained in:
parent
3c72960bd1
commit
b920ecae86
7 changed files with 290 additions and 1 deletions
1
public/images/floating-cogs.svg
Normal file
1
public/images/floating-cogs.svg
Normal file
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 32 KiB |
|
@ -1,7 +1,9 @@
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { Admin, Resource, resolveBrowserLocale } from "react-admin";
|
import { Admin, Resource, resolveBrowserLocale } from "react-admin";
|
||||||
import polyglotI18nProvider from "ra-i18n-polyglot";
|
import polyglotI18nProvider from "ra-i18n-polyglot";
|
||||||
|
import authProvider from "./synapse/authProvider";
|
||||||
import dataProvider from "./dataProvider";
|
import dataProvider from "./dataProvider";
|
||||||
|
import LoginPage from "./components/LoginPage";
|
||||||
import germanMessages from "./i18n/de";
|
import germanMessages from "./i18n/de";
|
||||||
import englishMessages from "./i18n/en";
|
import englishMessages from "./i18n/en";
|
||||||
|
|
||||||
|
@ -16,7 +18,12 @@ const i18nProvider = polyglotI18nProvider(
|
||||||
);
|
);
|
||||||
|
|
||||||
const App = () => (
|
const App = () => (
|
||||||
<Admin dataProvider={dataProvider} i18nProvider={i18nProvider}>
|
<Admin
|
||||||
|
loginPage={LoginPage}
|
||||||
|
authProvider={authProvider}
|
||||||
|
dataProvider={dataProvider}
|
||||||
|
i18nProvider={i18nProvider}
|
||||||
|
>
|
||||||
<Resource name="data" />
|
<Resource name="data" />
|
||||||
</Admin>
|
</Admin>
|
||||||
);
|
);
|
||||||
|
|
198
src/components/LoginPage.js
Normal file
198
src/components/LoginPage.js
Normal file
|
@ -0,0 +1,198 @@
|
||||||
|
import React, { useState } from "react";
|
||||||
|
import {
|
||||||
|
Notification,
|
||||||
|
useLogin,
|
||||||
|
useNotify,
|
||||||
|
useLocale,
|
||||||
|
useSetLocale,
|
||||||
|
useTranslate,
|
||||||
|
} from "react-admin";
|
||||||
|
import { Field, Form } from "react-final-form";
|
||||||
|
import {
|
||||||
|
Avatar,
|
||||||
|
Button,
|
||||||
|
Card,
|
||||||
|
CardActions,
|
||||||
|
CircularProgress,
|
||||||
|
MenuItem,
|
||||||
|
Select,
|
||||||
|
TextField,
|
||||||
|
} from "@material-ui/core";
|
||||||
|
import { makeStyles } from "@material-ui/core/styles";
|
||||||
|
import LockIcon from "@material-ui/icons/Lock";
|
||||||
|
|
||||||
|
const useStyles = makeStyles(theme => ({
|
||||||
|
main: {
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "column",
|
||||||
|
minHeight: "100vh",
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "flex-start",
|
||||||
|
background: "url(./images/floating-cogs.svg)",
|
||||||
|
backgroundColor: "#f9f9f9",
|
||||||
|
backgroundRepeat: "no-repeat",
|
||||||
|
backgroundSize: "cover",
|
||||||
|
},
|
||||||
|
card: {
|
||||||
|
minWidth: 300,
|
||||||
|
marginTop: "6em",
|
||||||
|
},
|
||||||
|
avatar: {
|
||||||
|
margin: "1em",
|
||||||
|
display: "flex",
|
||||||
|
justifyContent: "center",
|
||||||
|
},
|
||||||
|
icon: {
|
||||||
|
backgroundColor: theme.palette.secondary.main,
|
||||||
|
},
|
||||||
|
hint: {
|
||||||
|
marginTop: "1em",
|
||||||
|
display: "flex",
|
||||||
|
justifyContent: "center",
|
||||||
|
color: theme.palette.grey[500],
|
||||||
|
},
|
||||||
|
form: {
|
||||||
|
padding: "0 1em 1em 1em",
|
||||||
|
},
|
||||||
|
input: {
|
||||||
|
marginTop: "1em",
|
||||||
|
},
|
||||||
|
actions: {
|
||||||
|
padding: "0 1em 1em 1em",
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
const LoginPage = ({ theme }) => {
|
||||||
|
const classes = useStyles({ theme });
|
||||||
|
const login = useLogin();
|
||||||
|
const notify = useNotify();
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
var locale = useLocale();
|
||||||
|
const setLocale = useSetLocale();
|
||||||
|
const translate = useTranslate();
|
||||||
|
const homeserver = localStorage.getItem("home_server");
|
||||||
|
|
||||||
|
const renderInput = ({
|
||||||
|
meta: { touched, error } = {},
|
||||||
|
input: { ...inputProps },
|
||||||
|
...props
|
||||||
|
}) => (
|
||||||
|
<TextField
|
||||||
|
error={!!(touched && error)}
|
||||||
|
helperText={touched && error}
|
||||||
|
{...inputProps}
|
||||||
|
{...props}
|
||||||
|
fullWidth
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
const validate = values => {
|
||||||
|
const errors = {};
|
||||||
|
if (!values.homeserver) {
|
||||||
|
errors.homeserver = translate("ra.validation.required");
|
||||||
|
}
|
||||||
|
if (!values.username) {
|
||||||
|
errors.username = translate("ra.validation.required");
|
||||||
|
}
|
||||||
|
if (!values.password) {
|
||||||
|
errors.password = translate("ra.validation.required");
|
||||||
|
}
|
||||||
|
return errors;
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSubmit = auth => {
|
||||||
|
setLoading(true);
|
||||||
|
login(auth).catch(error => {
|
||||||
|
setLoading(false);
|
||||||
|
notify(
|
||||||
|
typeof error === "string"
|
||||||
|
? error
|
||||||
|
: typeof error === "undefined" || !error.message
|
||||||
|
? "ra.auth.sign_in_error"
|
||||||
|
: error.message,
|
||||||
|
"warning"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Form
|
||||||
|
initialValues={{ homeserver: homeserver }}
|
||||||
|
onSubmit={handleSubmit}
|
||||||
|
validate={validate}
|
||||||
|
render={({ handleSubmit }) => (
|
||||||
|
<form onSubmit={handleSubmit} noValidate>
|
||||||
|
<div className={classes.main}>
|
||||||
|
<Card className={classes.card}>
|
||||||
|
<div className={classes.avatar}>
|
||||||
|
<Avatar className={classes.icon}>
|
||||||
|
<LockIcon />
|
||||||
|
</Avatar>
|
||||||
|
</div>
|
||||||
|
<div className={classes.hint}>
|
||||||
|
{translate("synapseadmin.auth.welcome")}
|
||||||
|
</div>
|
||||||
|
<div className={classes.form}>
|
||||||
|
<div className={classes.input}>
|
||||||
|
<Select
|
||||||
|
value={locale}
|
||||||
|
onChange={e => {
|
||||||
|
setLocale(e.target.value);
|
||||||
|
}}
|
||||||
|
fullWidth
|
||||||
|
disabled={loading}
|
||||||
|
>
|
||||||
|
<MenuItem value="de">Deutsch</MenuItem>
|
||||||
|
<MenuItem value="en">English</MenuItem>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
<div className={classes.input}>
|
||||||
|
<Field
|
||||||
|
autoFocus
|
||||||
|
name="homeserver"
|
||||||
|
component={renderInput}
|
||||||
|
label={translate("synapseadmin.auth.homeserver")}
|
||||||
|
disabled={loading}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className={classes.input}>
|
||||||
|
<Field
|
||||||
|
name="username"
|
||||||
|
component={renderInput}
|
||||||
|
label={translate("ra.auth.username")}
|
||||||
|
disabled={loading}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className={classes.input}>
|
||||||
|
<Field
|
||||||
|
name="password"
|
||||||
|
component={renderInput}
|
||||||
|
label={translate("ra.auth.password")}
|
||||||
|
type="password"
|
||||||
|
disabled={loading}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<CardActions className={classes.actions}>
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
type="submit"
|
||||||
|
color="primary"
|
||||||
|
disabled={loading}
|
||||||
|
className={classes.button}
|
||||||
|
fullWidth
|
||||||
|
>
|
||||||
|
{loading && <CircularProgress size={25} thickness={2} />}
|
||||||
|
{translate("ra.auth.sign_in")}
|
||||||
|
</Button>
|
||||||
|
</CardActions>
|
||||||
|
</Card>
|
||||||
|
<Notification />
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default LoginPage;
|
14
src/components/LoginPage.test.js
Normal file
14
src/components/LoginPage.test.js
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
import React from "react";
|
||||||
|
import { TestContext } from "react-admin";
|
||||||
|
import { shallow } from "enzyme";
|
||||||
|
import LoginPage from "./LoginPage";
|
||||||
|
|
||||||
|
describe("LoginForm", () => {
|
||||||
|
it("renders", () => {
|
||||||
|
shallow(
|
||||||
|
<TestContext>
|
||||||
|
<LoginPage />
|
||||||
|
</TestContext>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
|
@ -2,4 +2,10 @@ import germanMessages from "ra-language-german";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
...germanMessages,
|
...germanMessages,
|
||||||
|
synapseadmin: {
|
||||||
|
auth: {
|
||||||
|
homeserver: "Heimserver",
|
||||||
|
welcome: "Willkommen bei Synapse-admin",
|
||||||
|
},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -2,4 +2,10 @@ import englishMessages from "ra-language-english";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
...englishMessages,
|
...englishMessages,
|
||||||
|
synapseadmin: {
|
||||||
|
auth: {
|
||||||
|
homeserver: "Homeserver",
|
||||||
|
welcome: "Welcome to Synapse-admin",
|
||||||
|
},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
57
src/synapse/authProvider.js
Normal file
57
src/synapse/authProvider.js
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
import { fetchUtils } from "react-admin";
|
||||||
|
|
||||||
|
const authProvider = {
|
||||||
|
// called when the user attempts to log in
|
||||||
|
login: ({ homeserver, username, password }) => {
|
||||||
|
console.log("login ");
|
||||||
|
const options = {
|
||||||
|
method: "POST",
|
||||||
|
body: JSON.stringify({
|
||||||
|
type: "m.login.password",
|
||||||
|
user: username,
|
||||||
|
password: password,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
// add 'https://' to homeserver url if its missing
|
||||||
|
let newUrl = window.decodeURIComponent(homeserver);
|
||||||
|
newUrl = newUrl.trim().replace(/\s/g, "");
|
||||||
|
if (!/^https?:\/\//i.test(newUrl)) {
|
||||||
|
homeserver = `https://${newUrl}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const url = homeserver + "/_matrix/client/r0/login";
|
||||||
|
return fetchUtils.fetchJson(url, options).then(({ json }) => {
|
||||||
|
localStorage.setItem("home_server", json.home_server);
|
||||||
|
localStorage.setItem("user_id", json.user_id);
|
||||||
|
localStorage.setItem("access_token", json.access_token);
|
||||||
|
localStorage.setItem("device_id", json.device_id);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
// called when the user clicks on the logout button
|
||||||
|
logout: () => {
|
||||||
|
console.log("logout ");
|
||||||
|
localStorage.removeItem("access_token");
|
||||||
|
return Promise.resolve();
|
||||||
|
},
|
||||||
|
// called when the API returns an error
|
||||||
|
checkError: ({ status }) => {
|
||||||
|
console.log("checkError " + status);
|
||||||
|
if (status === 401 || status === 403) {
|
||||||
|
return Promise.reject();
|
||||||
|
}
|
||||||
|
return Promise.resolve();
|
||||||
|
},
|
||||||
|
// called when the user navigates to a new location, to check for authentication
|
||||||
|
checkAuth: () => {
|
||||||
|
const access_token = localStorage.getItem("access_token");
|
||||||
|
console.log("checkAuth " + access_token);
|
||||||
|
return typeof access_token == "string"
|
||||||
|
? Promise.resolve()
|
||||||
|
: Promise.reject();
|
||||||
|
},
|
||||||
|
// called when the user navigates to a new location, to check for permissions / roles
|
||||||
|
getPermissions: () => Promise.resolve(),
|
||||||
|
};
|
||||||
|
|
||||||
|
export default authProvider;
|
Loading…
Reference in a new issue