diff --git a/package.json b/package.json index a167252..d764fc4 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,10 @@ "prettier": "^1.19.1" }, "dependencies": { + "@progress/kendo-drawing": "^1.6.0", + "@progress/kendo-react-pdf": "^3.10.1", "prop-types": "^15.7.2", + "qrcode.react": "^1.0.0", "ra-language-german": "^2.1.2", "react": "^16.12.0", "react-admin": "^3.1.3", diff --git a/public/images/logo.png b/public/images/logo.png new file mode 100644 index 0000000..d16a65a Binary files /dev/null and b/public/images/logo.png differ diff --git a/src/App.js b/src/App.js index ce37344..f5ffab0 100644 --- a/src/App.js +++ b/src/App.js @@ -10,6 +10,8 @@ import UserIcon from "@material-ui/icons/Group"; import { ViewListIcon as RoomIcon } from "@material-ui/icons/ViewList"; import germanMessages from "./i18n/de"; 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? const messages = { @@ -27,6 +29,9 @@ const App = () => ( authProvider={authProvider} dataProvider={dataProvider} i18nProvider={i18nProvider} + customRoutes={[ + , + ]} > 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 = ; + displayname = props.location.state.displayname; + } + + return ( +
+ + <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; diff --git a/src/components/users.js b/src/components/users.js index d3b6b9c..cf37680 100644 --- a/src/components/users.js +++ b/src/components/users.js @@ -13,6 +13,8 @@ import { TextField, TextInput, ReferenceField, + SaveButton, + Toolbar, regex, } 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 const validateUser = regex( /^@[a-z0-9._=\-/]+:.*/, @@ -109,7 +139,7 @@ const validateUser = regex( export const UserCreate = props => ( <Create record={generateRandomUser()} {...props}> - <SimpleForm> + <SimpleForm toolbar={<UserCreateToolbar />}> <TextInput source="id" autoComplete="off" validate={validateUser} /> <TextInput source="displayname" /> <PasswordInput source="password" autoComplete="new-password" /> @@ -120,7 +150,7 @@ export const UserCreate = props => ( export const UserEdit = props => ( <Edit {...props}> - <SimpleForm> + <SimpleForm toolbar={<UserCreateToolbar />}> <TextInput source="id" disabled /> <TextInput source="displayname" /> <PasswordInput source="password" autoComplete="new-password" /> diff --git a/src/i18n/de.js b/src/i18n/de.js index 6626ae7..2500630 100644 --- a/src/i18n/de.js +++ b/src/i18n/de.js @@ -7,6 +7,11 @@ export default { homeserver: "Heimserver", welcome: "Willkommen bei Synapse-admin", }, + action: { + save_and_show: "QR Code erzeugen", + save_only: "Speichern", + download_pdf: "PDF speichern", + }, users: { invalid_user_id: "Muss eine vollständige Matrix Benutzer-ID sein, z.B. @benutzer_id:homeserver", diff --git a/src/i18n/en.js b/src/i18n/en.js index 0683213..bd2365c 100644 --- a/src/i18n/en.js +++ b/src/i18n/en.js @@ -7,6 +7,11 @@ export default { homeserver: "Homeserver", welcome: "Welcome to Synapse-admin", }, + action: { + save_and_show: "Create QR code", + save_only: "Save", + download_pdf: "Download PDF", + }, users: { invalid_user_id: "Must be a fully qualified Matrix user-id, e.g. @user_id:homeserver", diff --git a/yarn.lock b/yarn.lock index c1a23ab..cef1706 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1187,6 +1187,25 @@ resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-1.1.3.tgz#2b5a3ab3f918cca48a8c754c08168e3f03eba61b" 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": version "1.1.3" 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" integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== -pako@~1.0.5: +pako@^1.0.5, pako@~1.0.5: version "1.0.11" resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.11.tgz#6c9599d340d54dfd3946380252a35705a6b992bf" 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" 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: version "6.7.0" resolved "https://registry.yarnpkg.com/qs/-/qs-6.7.0.tgz#41dc1a015e3d581f1621776be31afb2876a9b1bc"