diff --git a/src/App.js b/src/App.js
index 4bf5fc5..b64587e 100644
--- a/src/App.js
+++ b/src/App.js
@@ -40,6 +40,7 @@ const App = () => (
dataProvider={dataProvider}
i18nProvider={i18nProvider}
customRoutes={[
+ ,
,
]}
>
diff --git a/src/components/ImportFeature.js b/src/components/ImportFeature.js
index 565855d..6ba745a 100644
--- a/src/components/ImportFeature.js
+++ b/src/components/ImportFeature.js
@@ -20,6 +20,7 @@ import {
import { useTranslate } from "ra-core";
import Container from "@material-ui/core/Container/Container";
import { generateRandomUser } from "./users";
+import ShowUserPdf from "./ShowUserPdf";
const LOGGING = true;
@@ -59,6 +60,8 @@ const FilePicker = props => {
const [progress, setProgress] = useState(null);
+ const [pdfRecords, setPdfRecords] = useState(null);
+
const [importResults, setImportResults] = useState(null);
const [skippedRecords, setSkippedRecords] = useState(null);
@@ -66,17 +69,23 @@ const FilePicker = props => {
const [passwordMode, setPasswordMode] = useState(true);
const [useridMode, setUseridMode] = useState("ignore");
+ const [showingPdf, setShowingPdf] = useState(false);
+
const translate = useTranslate();
const notify = useNotify();
const dataProvider = useDataProvider();
const onFileChange = async e => {
- if (progress !== null) return;
-
+ if (progress !== null) {
+ return;
+ }
+ if (LOGGING) console.log("onFileChange was called");
setValues(null);
setError(null);
setStats(null);
+ setPdfRecords(null);
+
setImportResults(null);
const file = e.target.files ? e.target.files[0] : null;
/* Let's refuse some unreasonably big files instead of freezing
@@ -126,6 +135,11 @@ const FilePicker = props => {
});
if (eF.length !== 0) {
+ if (LOGGING) {
+ console.log(meta.fields);
+ console.log(eF);
+ console.log(oF);
+ }
setError(
translate("import_users.error.required_field", { field: eF[0] })
);
@@ -226,6 +240,9 @@ const FilePicker = props => {
setProgress,
setError
);
+
+ setPdfRecords(results.recordsForPdf);
+
setImportResults(results);
// offer CSV download of skipped or errored records
// (so that the user doesn't have to filter out successful
@@ -251,6 +268,8 @@ const FilePicker = props => {
let skippedRecords = [];
let erroredRecords = [];
let succeededRecords = [];
+ let recordsForPdf = [];
+
let changeStats = {
toAdmin: 0,
toGuest: 0,
@@ -365,6 +384,14 @@ const FilePicker = props => {
await dataProvider.create("users", { data: recordData });
}
succeededRecords.push(recordData);
+
+ if (recordData.password !== undefined) {
+ recordsForPdf.push({
+ id: recordData.id,
+ password: recordData.password,
+ displayname: recordData.displayname,
+ });
+ }
}
);
};
@@ -389,6 +416,7 @@ const FilePicker = props => {
erroredRecords,
succeededRecords,
totalRecordCount: entriesCount,
+ recordsForPdf,
changeStats,
wasDryRun: dryRun,
};
@@ -618,6 +646,10 @@ const FilePicker = props => {
,
]
: ""}
+ {translate(
+ "import_users.cards.results.for_print",
+ importResults.recordsForPdf.length
+ )}
{importResults.wasDryRun && [
translate("import_users.cards.results.simulated_only"),
@@ -655,20 +687,43 @@ const FilePicker = props => {
);
- let allCards = [];
- if (uploadCard) allCards.push(uploadCard);
- if (errorCards) allCards.push(errorCards);
- if (conflictCards) allCards.push(conflictCards);
- if (statsCards) allCards.push(...statsCards);
- if (startImportCard) allCards.push(startImportCard);
- if (resultsCard) allCards.push(resultsCard);
+ let pdfDisplay =
+ pdfRecords && showingPdf && pdfRecords.length ? (
+
+ ) : null;
- let cardContainer = {allCards};
+ let pdfActions = pdfRecords ? (
+
+
+
+ ) : null;
- return [
-
,
- cardContainer,
- ];
+ if (pdfRecords && showingPdf) {
+ return {pdfDisplay};
+ } else {
+ let allCards = [];
+ if (uploadCard) allCards.push(uploadCard);
+ if (errorCards) allCards.push(errorCards);
+ if (conflictCards) allCards.push(conflictCards);
+ if (statsCards) allCards.push(...statsCards);
+ if (startImportCard) allCards.push(startImportCard);
+ if (resultsCard) allCards.push(resultsCard);
+ if (pdfActions) allCards.push(pdfActions);
+
+ let cardContainer = {allCards};
+
+ return [
+ ,
+ cardContainer,
+ ];
+ }
};
export const ImportFeature = FilePicker;
diff --git a/src/components/ShowUserPdf.js b/src/components/ShowUserPdf.js
index ffcc5b6..cb380f1 100644
--- a/src/components/ShowUserPdf.js
+++ b/src/components/ShowUserPdf.js
@@ -1,8 +1,9 @@
-import React from "react";
+import React, { useRef } 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";
+import { string, any } from "prop-types";
function xor(a, b) {
var res = "";
@@ -14,109 +15,204 @@ function xor(a, b) {
function calculateQrString(serverUrl, username, password) {
const magicString = "wo9k5tep252qxsa5yde7366kugy6c01w7oeeya9hrmpf0t7ii7";
- var urlString = "user=" + username + "&password=" + password;
+ const origUrlString = "user=" + username + "&password=" + password;
- urlString = xor(urlString, magicString); // xor with magic string
+ var urlString = xor(origUrlString, magicString); // xor with magic string
+ if (origUrlString !== xor(urlString, magicString)) {
+ console.error(
+ "xoring this url string with magicString twice gave different results:",
+ origUrlString,
+ urlString,
+ xor(urlString, magicString)
+ );
+ }
urlString = btoa(urlString); // to base64
return serverUrl + "/#" + urlString;
}
+UserPdfPage.propTypes = {
+ classes: any,
+ displayname: string,
+ qrCode: any,
+ serverUrl: string,
+ username: string,
+ password: string,
+};
+
+function UserPdfPage({
+ classes,
+ displayname,
+ qrCode,
+ serverUrl,
+ username,
+ password,
+}) {
+ return (
+
+
+
{displayname}
+
+
+
+
+
+
+
+
+ Ihr persönlicher Anmeldecode:
+
+ |
+
+
+ Ihre persönlichen Zugangsdaten:
+
+ |
+
+
+
+ {qrCode}
+ |
+
+
+
+
+
+
+ Heimserver: |
+
+
+ {serverUrl}
+
+ |
+
+
+ Benutzername: |
+
+
+ {username}
+
+ |
+
+
+ Passwort: |
+
+
+ {password}
+
+ |
+
+
+
+
+ |
+
+
+
+
+ Hier können Sie Ihre selbst gewählte Schlüsselsicherungs-Passphrase
+ notieren:
+
+
+
+
+
+
+
+ );
+}
+
+const useStyles = makeStyles(theme => ({
+ page: {
+ height: 800,
+ width: 566,
+ padding: "none",
+ backgroundColor: "white",
+ boxShadow: "5px 5px 5px black",
+ margin: "auto",
+ overflowX: "hidden",
+ overflowY: "hidden",
+ fontFamily: "DejaVu Sans, Sans-Serif",
+ fontSize: 15,
+ },
+ header: {
+ height: 144,
+ width: 534,
+ marginLeft: 32,
+ marginTop: 15,
+ },
+ name: {
+ width: 240,
+ fontSize: 35,
+ float: "left",
+ marginTop: 100,
+ },
+ logo: {
+ width: 90,
+ marginTop: 50,
+ marginRight: 70,
+ float: "right",
+ },
+ body: {
+ clear: "both",
+ },
+ table_cell: {
+ verticalAlign: "top",
+ },
+ code_note: {
+ marginLeft: 32,
+ marginTop: 86,
+ },
+ qr: {
+ marginTop: 15,
+ marginLeft: 32,
+ },
+ credentials_note: {
+ marginTop: 86,
+ marginLeft: 10,
+ },
+ credentials_text: {
+ marginLeft: 10,
+ fontSize: 12,
+ },
+ credentials: {
+ fontFamily: "DejaVu Sans Mono, monospace",
+ },
+ note: {
+ fontSize: 18,
+ marginTop: 100,
+ marginLeft: 32,
+ marginRight: 32,
+ },
+}));
+
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",
- fontFamily: "DejaVu Sans, Sans-Serif",
- fontSize: 15,
- },
- header: {
- height: 144,
- width: 534,
- marginLeft: 32,
- marginTop: 15,
- },
- name: {
- width: 240,
- fontSize: 35,
- float: "left",
- marginTop: 100,
- },
- logo: {
- width: 90,
- marginTop: 50,
- marginRight: 70,
- float: "right",
- },
- body: {
- clear: "both",
- },
- table_cell: {
- verticalAlign: "top",
- },
- code_note: {
- marginLeft: 32,
- marginTop: 86,
- },
- qr: {
- marginTop: 15,
- marginLeft: 32,
- },
- credentials_note: {
- marginTop: 86,
- marginLeft: 10,
- },
- credentials_text: {
- marginLeft: 10,
- fontSize: 12,
- },
- credentials: {
- fontFamily: "DejaVu Sans Mono, monospace",
- },
- note: {
- fontSize: 18,
- marginTop: 100,
- marginLeft: 32,
- marginRight: 32,
- },
- }));
-
const classes = useStyles();
-
- var resume;
+ const userPdf = useRef(null);
const exportPDF = () => {
- resume.save();
+ userPdf.current.save();
};
- var qrCode = "";
- var displayname = "";
- var id = "";
- var password = "";
- var username = "";
- var serverUrl = "";
+ let userRecords;
+
+ if (props.records) {
+ userRecords = props.records;
+ }
if (
+ props.location &&
props.location.state &&
props.location.state.id &&
props.location.state.password
) {
- id = props.location.state.id;
- password = props.location.state.password;
-
- username = id.substring(1, id.indexOf(":"));
- serverUrl = "https://" + id.substring(id.indexOf(":") + 1);
-
- const qrString = calculateQrString(serverUrl, username, password);
-
- qrCode = ;
- displayname = props.location.state.displayname;
+ userRecords = [
+ {
+ id: props.location.state.id,
+ password: props.location.state.password,
+ displayname: props.location.state.displayname,
+ },
+ ];
}
return (
@@ -126,82 +222,39 @@ const ShowUserPdf = props => {
(resume = r)}
+ ref={userPdf}
+ //ref={r => (resume = r)}
>
-
-
-
{displayname}
-
-
-
-
-
-
-
-
- Ihr persönlicher Anmeldecode:
-
- |
-
-
- Ihre persönlichen Zugangsdaten:
-
- |
-
-
-
- {qrCode}
- |
-
-
-
-
-
-
- Heimserver: |
-
-
- {serverUrl}
-
- |
-
-
- Benutzername: |
-
-
- {username}
-
- |
-
-
- Passwort: |
-
-
- {password}
-
- |
-
-
-
-
- |
-
-
-
-
- Hier können Sie Ihre selbst gewählte
- Schlüsselsicherungs-Passphrase notieren:
-
-
-
-
-
-
-
+ {userRecords.map(record => {
+ if (record.id && record.password) {
+ const username = record.id.substring(1, record.id.indexOf(":"));
+ const serverUrl =
+ "https://" + record.id.substring(record.id.indexOf(":") + 1);
+ const qrString = calculateQrString(
+ serverUrl,
+ username,
+ record.password
+ );
+ const qrCode = ;
+ return (
+
+ );
+ } else {
+ /* Skip empty PDF pages */
+ return null;
+ }
+ })}
);
diff --git a/src/components/users.js b/src/components/users.js
index efefd09..fe7a84a 100644
--- a/src/components/users.js
+++ b/src/components/users.js
@@ -12,10 +12,12 @@ import {
ArrayInput,
ArrayField,
Button,
+ CreateButton,
Datagrid,
DateField,
Create,
Edit,
+ ExportButton,
List,
Filter,
Toolbar,
@@ -37,11 +39,8 @@ import {
DeleteButton,
SaveButton,
regex,
- useRedirect,
useTranslate,
Pagination,
- CreateButton,
- ExportButton,
TopToolbar,
sanitizeListRestProps,
NumberField,
@@ -50,6 +49,13 @@ import SaveQrButton from "./SaveQrButton";
import { ServerNoticeButton, ServerNoticeBulkButton } from "./ServerNotices";
import { DeviceRemoveButton } from "./devices";
import { makeStyles } from "@material-ui/core/styles";
+import { Link } from "react-router-dom";
+
+const redirect = (basePath, id, data) => {
+ return {
+ pathname: "/importcsv",
+ };
+};
const useStyles = makeStyles({
small: {
@@ -81,7 +87,6 @@ const UserListActions = ({
total,
...rest
}) => {
- const redirectTo = useRedirect();
return (
{filters &&
@@ -101,6 +106,10 @@ const UserListActions = ({
exporter={exporter}
maxResults={maxResults}
/>
+ {/* Add your custom actions */}
+
);
};
@@ -178,7 +187,7 @@ export const UserList = props => {
};
// redirect to the related Author show page
-const redirect = (basePath, id, data) => {
+const redirectToPdf = (basePath, id, data) => {
return {
pathname: "/showpdf",
state: {
@@ -193,7 +202,7 @@ const UserCreateToolbar = props => (
{
return `${homeserver}/_matrix/media/r0/thumbnail/${serverName}/${mediaId}?width=24&height=24&method=scale`;
};
-const powerLevelToRole = powerLevel =>
- powerLevel < 100 ? (powerLevel < 50 ? "user" : "mod") : "admin";
-
const POWER_LEVELS = {
admin: 100,
mod: 50,