Create PDF with QR code on user create/edit
Change-Id: Ib89b68e956d96002ddbf6ac5ddcaea73b5b3e3fb
This commit is contained in:
parent
c9bce409d2
commit
0f7e4c1909
@ -17,7 +17,10 @@
|
|||||||
"prettier": "^1.19.1"
|
"prettier": "^1.19.1"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@progress/kendo-drawing": "^1.6.0",
|
||||||
|
"@progress/kendo-react-pdf": "^3.10.1",
|
||||||
"prop-types": "^15.7.2",
|
"prop-types": "^15.7.2",
|
||||||
|
"qrcode.react": "^1.0.0",
|
||||||
"ra-language-german": "^2.1.2",
|
"ra-language-german": "^2.1.2",
|
||||||
"react": "^16.12.0",
|
"react": "^16.12.0",
|
||||||
"react-admin": "^3.1.3",
|
"react-admin": "^3.1.3",
|
||||||
|
BIN
public/images/logo.png
Normal file
BIN
public/images/logo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 358 KiB |
@ -10,6 +10,8 @@ import UserIcon from "@material-ui/icons/Group";
|
|||||||
import { ViewListIcon as RoomIcon } from "@material-ui/icons/ViewList";
|
import { ViewListIcon as RoomIcon } from "@material-ui/icons/ViewList";
|
||||||
import germanMessages from "./i18n/de";
|
import germanMessages from "./i18n/de";
|
||||||
import englishMessages from "./i18n/en";
|
import englishMessages from "./i18n/en";
|
||||||
|
import ShowUserPdf from "./components/ShowUserPdf";
|
||||||
|
import { Route } from "react-router-dom";
|
||||||
|
|
||||||
// TODO: Can we use lazy loading together with browser locale?
|
// TODO: Can we use lazy loading together with browser locale?
|
||||||
const messages = {
|
const messages = {
|
||||||
@ -27,6 +29,9 @@ const App = () => (
|
|||||||
authProvider={authProvider}
|
authProvider={authProvider}
|
||||||
dataProvider={dataProvider}
|
dataProvider={dataProvider}
|
||||||
i18nProvider={i18nProvider}
|
i18nProvider={i18nProvider}
|
||||||
|
customRoutes={[
|
||||||
|
<Route key="showpdf" path="/showpdf" component={ShowUserPdf} />,
|
||||||
|
]}
|
||||||
>
|
>
|
||||||
<Resource
|
<Resource
|
||||||
name="users"
|
name="users"
|
||||||
|
135
src/components/ShowUserPdf.js
Normal file
135
src/components/ShowUserPdf.js
Normal file
@ -0,0 +1,135 @@
|
|||||||
|
import React from "react";
|
||||||
|
import { Title, Button } from "react-admin";
|
||||||
|
import { makeStyles } from "@material-ui/core/styles";
|
||||||
|
import { PDFExport } from "@progress/kendo-react-pdf";
|
||||||
|
import QRCode from "qrcode.react";
|
||||||
|
|
||||||
|
function xor(a, b) {
|
||||||
|
var res = "",
|
||||||
|
i = a.length,
|
||||||
|
j = b.length;
|
||||||
|
while (i-- > 0 && j-- > 0)
|
||||||
|
res = (parseInt(a.charAt(i)) ^ parseInt(b.charAt(j))).toString() + res;
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
function calculateQrString(serverUrl, username, password) {
|
||||||
|
var magicString = "wo9k5tep252qxsa5yde7366kugy6c01w7oeeya9hrmpf0t7ii7";
|
||||||
|
|
||||||
|
var urlString = "user=" + username + "&password=" + password;
|
||||||
|
|
||||||
|
while (urlString.length > magicString.length) {
|
||||||
|
magicString += magicString;
|
||||||
|
}
|
||||||
|
|
||||||
|
urlString = xor(urlString, magicString); // xor with magic string
|
||||||
|
urlString = btoa(urlString); // to base64
|
||||||
|
|
||||||
|
return serverUrl + "/#" + urlString;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ShowUserPdf = props => {
|
||||||
|
const useStyles = makeStyles(theme => ({
|
||||||
|
page: {
|
||||||
|
height: 800,
|
||||||
|
width: 566,
|
||||||
|
padding: "none",
|
||||||
|
backgroundColor: "white",
|
||||||
|
boxShadow: "5px 5px 5px black",
|
||||||
|
margin: "auto",
|
||||||
|
overflowX: "hidden",
|
||||||
|
overflowY: "hidden",
|
||||||
|
},
|
||||||
|
header: {
|
||||||
|
height: 144,
|
||||||
|
width: 534,
|
||||||
|
marginLeft: 32,
|
||||||
|
marginTop: 15,
|
||||||
|
},
|
||||||
|
name: {
|
||||||
|
width: 233,
|
||||||
|
fontSize: 40,
|
||||||
|
float: "left",
|
||||||
|
marginTop: 15,
|
||||||
|
},
|
||||||
|
logo: {
|
||||||
|
height: 90,
|
||||||
|
width: 90,
|
||||||
|
marginTop: 20,
|
||||||
|
marginRight: 32,
|
||||||
|
float: "left",
|
||||||
|
},
|
||||||
|
code: {
|
||||||
|
marginLeft: 330,
|
||||||
|
marginTop: 86,
|
||||||
|
},
|
||||||
|
qr: {
|
||||||
|
marginRight: 40,
|
||||||
|
float: "right",
|
||||||
|
},
|
||||||
|
note: {
|
||||||
|
fontSize: 18,
|
||||||
|
marginTop: 100,
|
||||||
|
marginLeft: 32,
|
||||||
|
marginRight: 32,
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
const classes = useStyles();
|
||||||
|
|
||||||
|
var resume;
|
||||||
|
|
||||||
|
const exportPDF = () => {
|
||||||
|
resume.save();
|
||||||
|
};
|
||||||
|
|
||||||
|
var qrCode = "";
|
||||||
|
var displayname = "";
|
||||||
|
|
||||||
|
if (props.location.state) {
|
||||||
|
const { id, password } = props.location.state;
|
||||||
|
|
||||||
|
const username = id.substring(1, id.indexOf(":"));
|
||||||
|
const serverUrl = "https://" + id.substring(id.indexOf(":") + 1);
|
||||||
|
|
||||||
|
const qrString = calculateQrString(serverUrl, username, password);
|
||||||
|
|
||||||
|
qrCode = <QRCode value={qrString} size={128} />;
|
||||||
|
displayname = props.location.state.displayname;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Title title="PDF" />
|
||||||
|
<Button label="synapseadmin.action.download_pdf" onClick={exportPDF} />
|
||||||
|
|
||||||
|
<PDFExport
|
||||||
|
paperSize={"A4"}
|
||||||
|
fileName="User.pdf"
|
||||||
|
title=""
|
||||||
|
subject=""
|
||||||
|
keywords=""
|
||||||
|
ref={r => (resume = r)}
|
||||||
|
>
|
||||||
|
<div className={classes.page}>
|
||||||
|
<div className={classes.code}>Ihr persönlicher Anmeldecode:</div>
|
||||||
|
<div className={classes.header}>
|
||||||
|
<div className={classes.name}>{displayname}</div>
|
||||||
|
<img className={classes.logo} alt="Logo" src="images/logo.png" />
|
||||||
|
<div className={classes.qr}>{qrCode}</div>
|
||||||
|
</div>
|
||||||
|
<div className={classes.note}>
|
||||||
|
Hier können Sie Ihre selbst gewählte Schlüsselsicherungs-Passphrase
|
||||||
|
notieren:
|
||||||
|
<br />
|
||||||
|
<br />
|
||||||
|
<br />
|
||||||
|
<hr />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</PDFExport>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ShowUserPdf;
|
@ -13,6 +13,8 @@ import {
|
|||||||
TextField,
|
TextField,
|
||||||
TextInput,
|
TextInput,
|
||||||
ReferenceField,
|
ReferenceField,
|
||||||
|
SaveButton,
|
||||||
|
Toolbar,
|
||||||
regex,
|
regex,
|
||||||
} from "react-admin";
|
} from "react-admin";
|
||||||
|
|
||||||
@ -101,6 +103,34 @@ function generateRandomUser() {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// redirect to the related Author show page
|
||||||
|
const redirect = (basePath, id, data) => {
|
||||||
|
return {
|
||||||
|
pathname: "/showpdf",
|
||||||
|
state: {
|
||||||
|
id: data.id,
|
||||||
|
displayname: data.displayname,
|
||||||
|
password: data.password,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const UserCreateToolbar = props => (
|
||||||
|
<Toolbar {...props}>
|
||||||
|
<SaveButton
|
||||||
|
label="synapseadmin.action.save_and_show"
|
||||||
|
redirect={redirect}
|
||||||
|
submitOnEnter={true}
|
||||||
|
/>
|
||||||
|
<SaveButton
|
||||||
|
label="synapseadmin.action.save_only"
|
||||||
|
redirect="show"
|
||||||
|
submitOnEnter={false}
|
||||||
|
variant="text"
|
||||||
|
/>
|
||||||
|
</Toolbar>
|
||||||
|
);
|
||||||
|
|
||||||
// https://matrix.org/docs/spec/appendices#user-identifiers
|
// https://matrix.org/docs/spec/appendices#user-identifiers
|
||||||
const validateUser = regex(
|
const validateUser = regex(
|
||||||
/^@[a-z0-9._=\-/]+:.*/,
|
/^@[a-z0-9._=\-/]+:.*/,
|
||||||
@ -109,7 +139,7 @@ const validateUser = regex(
|
|||||||
|
|
||||||
export const UserCreate = props => (
|
export const UserCreate = props => (
|
||||||
<Create record={generateRandomUser()} {...props}>
|
<Create record={generateRandomUser()} {...props}>
|
||||||
<SimpleForm>
|
<SimpleForm toolbar={<UserCreateToolbar />}>
|
||||||
<TextInput source="id" autoComplete="off" validate={validateUser} />
|
<TextInput source="id" autoComplete="off" validate={validateUser} />
|
||||||
<TextInput source="displayname" />
|
<TextInput source="displayname" />
|
||||||
<PasswordInput source="password" autoComplete="new-password" />
|
<PasswordInput source="password" autoComplete="new-password" />
|
||||||
@ -120,7 +150,7 @@ export const UserCreate = props => (
|
|||||||
|
|
||||||
export const UserEdit = props => (
|
export const UserEdit = props => (
|
||||||
<Edit {...props}>
|
<Edit {...props}>
|
||||||
<SimpleForm>
|
<SimpleForm toolbar={<UserCreateToolbar />}>
|
||||||
<TextInput source="id" disabled />
|
<TextInput source="id" disabled />
|
||||||
<TextInput source="displayname" />
|
<TextInput source="displayname" />
|
||||||
<PasswordInput source="password" autoComplete="new-password" />
|
<PasswordInput source="password" autoComplete="new-password" />
|
||||||
|
@ -7,6 +7,11 @@ export default {
|
|||||||
homeserver: "Heimserver",
|
homeserver: "Heimserver",
|
||||||
welcome: "Willkommen bei Synapse-admin",
|
welcome: "Willkommen bei Synapse-admin",
|
||||||
},
|
},
|
||||||
|
action: {
|
||||||
|
save_and_show: "QR Code erzeugen",
|
||||||
|
save_only: "Speichern",
|
||||||
|
download_pdf: "PDF speichern",
|
||||||
|
},
|
||||||
users: {
|
users: {
|
||||||
invalid_user_id:
|
invalid_user_id:
|
||||||
"Muss eine vollständige Matrix Benutzer-ID sein, z.B. @benutzer_id:homeserver",
|
"Muss eine vollständige Matrix Benutzer-ID sein, z.B. @benutzer_id:homeserver",
|
||||||
|
@ -7,6 +7,11 @@ export default {
|
|||||||
homeserver: "Homeserver",
|
homeserver: "Homeserver",
|
||||||
welcome: "Welcome to Synapse-admin",
|
welcome: "Welcome to Synapse-admin",
|
||||||
},
|
},
|
||||||
|
action: {
|
||||||
|
save_and_show: "Create QR code",
|
||||||
|
save_only: "Save",
|
||||||
|
download_pdf: "Download PDF",
|
||||||
|
},
|
||||||
users: {
|
users: {
|
||||||
invalid_user_id:
|
invalid_user_id:
|
||||||
"Must be a fully qualified Matrix user-id, e.g. @user_id:homeserver",
|
"Must be a fully qualified Matrix user-id, e.g. @user_id:homeserver",
|
||||||
|
35
yarn.lock
35
yarn.lock
@ -1187,6 +1187,25 @@
|
|||||||
resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-1.1.3.tgz#2b5a3ab3f918cca48a8c754c08168e3f03eba61b"
|
resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-1.1.3.tgz#2b5a3ab3f918cca48a8c754c08168e3f03eba61b"
|
||||||
integrity sha512-shAmDyaQC4H92APFoIaVDHCx5bStIocgvbwQyxPRrbUY20V1EYTbSDchWbuwlMG3V17cprZhA6+78JfB+3DTPw==
|
integrity sha512-shAmDyaQC4H92APFoIaVDHCx5bStIocgvbwQyxPRrbUY20V1EYTbSDchWbuwlMG3V17cprZhA6+78JfB+3DTPw==
|
||||||
|
|
||||||
|
"@progress/kendo-drawing@^1.6.0":
|
||||||
|
version "1.6.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@progress/kendo-drawing/-/kendo-drawing-1.6.0.tgz#66e9df431f52c7dd9fd5567be80dcbfa3a162281"
|
||||||
|
integrity sha512-9dGlFvW9fMgqAgcbLi+SfTeMUpNYdoVthwNxwAtsRQ+QwcgXJcdzFpLrLBXp17pXpDDFpiOyMqiwjffNGwtc3w==
|
||||||
|
dependencies:
|
||||||
|
pako "^1.0.5"
|
||||||
|
|
||||||
|
"@progress/kendo-file-saver@^1.0.1":
|
||||||
|
version "1.0.7"
|
||||||
|
resolved "https://registry.yarnpkg.com/@progress/kendo-file-saver/-/kendo-file-saver-1.0.7.tgz#5b602115d1b0b5e26f3e52451a3ed7c29ed76c51"
|
||||||
|
integrity sha512-8tsho/+DATzfTW4BBaHrkF3C3jqH2/bQ+XbjqA0KfmTiBRVK6ygK+tkvkYeDhFlQBbJ02MmJlEC6OmXvXRFkUg==
|
||||||
|
|
||||||
|
"@progress/kendo-react-pdf@^3.10.1":
|
||||||
|
version "3.10.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/@progress/kendo-react-pdf/-/kendo-react-pdf-3.10.1.tgz#348517daaddb366bbe840a92ec2fffbfd07ac2d2"
|
||||||
|
integrity sha512-2EKfQCwLFEa+mgCLKQ70iWVu7q2Dh/wJl6pPJ6Ix42BA7SiA2k5UmDk819FLY9pnMgv7WDxwqBP+8CvdkLoP5w==
|
||||||
|
dependencies:
|
||||||
|
"@progress/kendo-file-saver" "^1.0.1"
|
||||||
|
|
||||||
"@redux-saga/core@^1.1.3":
|
"@redux-saga/core@^1.1.3":
|
||||||
version "1.1.3"
|
version "1.1.3"
|
||||||
resolved "https://registry.yarnpkg.com/@redux-saga/core/-/core-1.1.3.tgz#3085097b57a4ea8db5528d58673f20ce0950f6a4"
|
resolved "https://registry.yarnpkg.com/@redux-saga/core/-/core-1.1.3.tgz#3085097b57a4ea8db5528d58673f20ce0950f6a4"
|
||||||
@ -7757,7 +7776,7 @@ p-try@^2.0.0:
|
|||||||
resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6"
|
resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6"
|
||||||
integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==
|
integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==
|
||||||
|
|
||||||
pako@~1.0.5:
|
pako@^1.0.5, pako@~1.0.5:
|
||||||
version "1.0.11"
|
version "1.0.11"
|
||||||
resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.11.tgz#6c9599d340d54dfd3946380252a35705a6b992bf"
|
resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.11.tgz#6c9599d340d54dfd3946380252a35705a6b992bf"
|
||||||
integrity sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==
|
integrity sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==
|
||||||
@ -8903,6 +8922,20 @@ q@^1.1.2:
|
|||||||
resolved "https://registry.yarnpkg.com/q/-/q-1.5.1.tgz#7e32f75b41381291d04611f1bf14109ac00651d7"
|
resolved "https://registry.yarnpkg.com/q/-/q-1.5.1.tgz#7e32f75b41381291d04611f1bf14109ac00651d7"
|
||||||
integrity sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc=
|
integrity sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc=
|
||||||
|
|
||||||
|
qr.js@0.0.0:
|
||||||
|
version "0.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/qr.js/-/qr.js-0.0.0.tgz#cace86386f59a0db8050fa90d9b6b0e88a1e364f"
|
||||||
|
integrity sha1-ys6GOG9ZoNuAUPqQ2baw6IoeNk8=
|
||||||
|
|
||||||
|
qrcode.react@^1.0.0:
|
||||||
|
version "1.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/qrcode.react/-/qrcode.react-1.0.0.tgz#7e8889db3b769e555e8eb463d4c6de221c36d5de"
|
||||||
|
integrity sha512-jBXleohRTwvGBe1ngV+62QvEZ/9IZqQivdwzo9pJM4LQMoCM2VnvNBnKdjvGnKyDZ/l0nCDgsPod19RzlPvm/Q==
|
||||||
|
dependencies:
|
||||||
|
loose-envify "^1.4.0"
|
||||||
|
prop-types "^15.6.0"
|
||||||
|
qr.js "0.0.0"
|
||||||
|
|
||||||
qs@6.7.0:
|
qs@6.7.0:
|
||||||
version "6.7.0"
|
version "6.7.0"
|
||||||
resolved "https://registry.yarnpkg.com/qs/-/qs-6.7.0.tgz#41dc1a015e3d581f1621776be31afb2876a9b1bc"
|
resolved "https://registry.yarnpkg.com/qs/-/qs-6.7.0.tgz#41dc1a015e3d581f1621776be31afb2876a9b1bc"
|
||||||
|
Loading…
Reference in New Issue
Block a user