Import users from CSV
Change-Id: Id05363ecc39aee4fdc4ac6afbcb039558b2a17ed
This commit is contained in:
parent
7b5c0e2845
commit
1aaa137afe
@ -40,6 +40,7 @@ const App = () => (
|
|||||||
dataProvider={dataProvider}
|
dataProvider={dataProvider}
|
||||||
i18nProvider={i18nProvider}
|
i18nProvider={i18nProvider}
|
||||||
customRoutes={[
|
customRoutes={[
|
||||||
|
<Route key="csvImport" path="/importcsv" component={ImportFeature} />,
|
||||||
<Route key="showpdf" path="/showpdf" component={ShowUserPdf} />,
|
<Route key="showpdf" path="/showpdf" component={ShowUserPdf} />,
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
|
@ -20,6 +20,7 @@ import {
|
|||||||
import { useTranslate } from "ra-core";
|
import { useTranslate } from "ra-core";
|
||||||
import Container from "@material-ui/core/Container/Container";
|
import Container from "@material-ui/core/Container/Container";
|
||||||
import { generateRandomUser } from "./users";
|
import { generateRandomUser } from "./users";
|
||||||
|
import ShowUserPdf from "./ShowUserPdf";
|
||||||
|
|
||||||
const LOGGING = true;
|
const LOGGING = true;
|
||||||
|
|
||||||
@ -59,6 +60,8 @@ const FilePicker = props => {
|
|||||||
|
|
||||||
const [progress, setProgress] = useState(null);
|
const [progress, setProgress] = useState(null);
|
||||||
|
|
||||||
|
const [pdfRecords, setPdfRecords] = useState(null);
|
||||||
|
|
||||||
const [importResults, setImportResults] = useState(null);
|
const [importResults, setImportResults] = useState(null);
|
||||||
const [skippedRecords, setSkippedRecords] = useState(null);
|
const [skippedRecords, setSkippedRecords] = useState(null);
|
||||||
|
|
||||||
@ -66,17 +69,23 @@ const FilePicker = props => {
|
|||||||
const [passwordMode, setPasswordMode] = useState(true);
|
const [passwordMode, setPasswordMode] = useState(true);
|
||||||
const [useridMode, setUseridMode] = useState("ignore");
|
const [useridMode, setUseridMode] = useState("ignore");
|
||||||
|
|
||||||
|
const [showingPdf, setShowingPdf] = useState(false);
|
||||||
|
|
||||||
const translate = useTranslate();
|
const translate = useTranslate();
|
||||||
const notify = useNotify();
|
const notify = useNotify();
|
||||||
|
|
||||||
const dataProvider = useDataProvider();
|
const dataProvider = useDataProvider();
|
||||||
|
|
||||||
const onFileChange = async e => {
|
const onFileChange = async e => {
|
||||||
if (progress !== null) return;
|
if (progress !== null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (LOGGING) console.log("onFileChange was called");
|
||||||
setValues(null);
|
setValues(null);
|
||||||
setError(null);
|
setError(null);
|
||||||
setStats(null);
|
setStats(null);
|
||||||
|
setPdfRecords(null);
|
||||||
|
|
||||||
setImportResults(null);
|
setImportResults(null);
|
||||||
const file = e.target.files ? e.target.files[0] : null;
|
const file = e.target.files ? e.target.files[0] : null;
|
||||||
/* Let's refuse some unreasonably big files instead of freezing
|
/* Let's refuse some unreasonably big files instead of freezing
|
||||||
@ -126,6 +135,11 @@ const FilePicker = props => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (eF.length !== 0) {
|
if (eF.length !== 0) {
|
||||||
|
if (LOGGING) {
|
||||||
|
console.log(meta.fields);
|
||||||
|
console.log(eF);
|
||||||
|
console.log(oF);
|
||||||
|
}
|
||||||
setError(
|
setError(
|
||||||
translate("import_users.error.required_field", { field: eF[0] })
|
translate("import_users.error.required_field", { field: eF[0] })
|
||||||
);
|
);
|
||||||
@ -226,6 +240,9 @@ const FilePicker = props => {
|
|||||||
setProgress,
|
setProgress,
|
||||||
setError
|
setError
|
||||||
);
|
);
|
||||||
|
|
||||||
|
setPdfRecords(results.recordsForPdf);
|
||||||
|
|
||||||
setImportResults(results);
|
setImportResults(results);
|
||||||
// offer CSV download of skipped or errored records
|
// offer CSV download of skipped or errored records
|
||||||
// (so that the user doesn't have to filter out successful
|
// (so that the user doesn't have to filter out successful
|
||||||
@ -251,6 +268,8 @@ const FilePicker = props => {
|
|||||||
let skippedRecords = [];
|
let skippedRecords = [];
|
||||||
let erroredRecords = [];
|
let erroredRecords = [];
|
||||||
let succeededRecords = [];
|
let succeededRecords = [];
|
||||||
|
let recordsForPdf = [];
|
||||||
|
|
||||||
let changeStats = {
|
let changeStats = {
|
||||||
toAdmin: 0,
|
toAdmin: 0,
|
||||||
toGuest: 0,
|
toGuest: 0,
|
||||||
@ -365,6 +384,14 @@ const FilePicker = props => {
|
|||||||
await dataProvider.create("users", { data: recordData });
|
await dataProvider.create("users", { data: recordData });
|
||||||
}
|
}
|
||||||
succeededRecords.push(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,
|
erroredRecords,
|
||||||
succeededRecords,
|
succeededRecords,
|
||||||
totalRecordCount: entriesCount,
|
totalRecordCount: entriesCount,
|
||||||
|
recordsForPdf,
|
||||||
changeStats,
|
changeStats,
|
||||||
wasDryRun: dryRun,
|
wasDryRun: dryRun,
|
||||||
};
|
};
|
||||||
@ -618,6 +646,10 @@ const FilePicker = props => {
|
|||||||
<br />,
|
<br />,
|
||||||
]
|
]
|
||||||
: ""}
|
: ""}
|
||||||
|
{translate(
|
||||||
|
"import_users.cards.results.for_print",
|
||||||
|
importResults.recordsForPdf.length
|
||||||
|
)}
|
||||||
<br />
|
<br />
|
||||||
{importResults.wasDryRun && [
|
{importResults.wasDryRun && [
|
||||||
translate("import_users.cards.results.simulated_only"),
|
translate("import_users.cards.results.simulated_only"),
|
||||||
@ -655,6 +687,27 @@ const FilePicker = props => {
|
|||||||
</CardActions>
|
</CardActions>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let pdfDisplay =
|
||||||
|
pdfRecords && showingPdf && pdfRecords.length ? (
|
||||||
|
<ShowUserPdf records={pdfRecords} />
|
||||||
|
) : null;
|
||||||
|
|
||||||
|
let pdfActions = pdfRecords ? (
|
||||||
|
<CardActions>
|
||||||
|
<Button
|
||||||
|
size="large"
|
||||||
|
onClick={e => {
|
||||||
|
setShowingPdf(true);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{translate("import_users.goToPdf")}
|
||||||
|
</Button>
|
||||||
|
</CardActions>
|
||||||
|
) : null;
|
||||||
|
|
||||||
|
if (pdfRecords && showingPdf) {
|
||||||
|
return <Card>{pdfDisplay}</Card>;
|
||||||
|
} else {
|
||||||
let allCards = [];
|
let allCards = [];
|
||||||
if (uploadCard) allCards.push(uploadCard);
|
if (uploadCard) allCards.push(uploadCard);
|
||||||
if (errorCards) allCards.push(errorCards);
|
if (errorCards) allCards.push(errorCards);
|
||||||
@ -662,6 +715,7 @@ const FilePicker = props => {
|
|||||||
if (statsCards) allCards.push(...statsCards);
|
if (statsCards) allCards.push(...statsCards);
|
||||||
if (startImportCard) allCards.push(startImportCard);
|
if (startImportCard) allCards.push(startImportCard);
|
||||||
if (resultsCard) allCards.push(resultsCard);
|
if (resultsCard) allCards.push(resultsCard);
|
||||||
|
if (pdfActions) allCards.push(pdfActions);
|
||||||
|
|
||||||
let cardContainer = <Card>{allCards}</Card>;
|
let cardContainer = <Card>{allCards}</Card>;
|
||||||
|
|
||||||
@ -669,6 +723,7 @@ const FilePicker = props => {
|
|||||||
<Title defaultTitle={translate("import_users.title")} />,
|
<Title defaultTitle={translate("import_users.title")} />,
|
||||||
cardContainer,
|
cardContainer,
|
||||||
];
|
];
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ImportFeature = FilePicker;
|
export const ImportFeature = FilePicker;
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
import React from "react";
|
import React, { useRef } from "react";
|
||||||
import { Title, Button } from "react-admin";
|
import { Title, Button } from "react-admin";
|
||||||
import { makeStyles } from "@material-ui/core/styles";
|
import { makeStyles } from "@material-ui/core/styles";
|
||||||
import { PDFExport } from "@progress/kendo-react-pdf";
|
import { PDFExport } from "@progress/kendo-react-pdf";
|
||||||
import QRCode from "qrcode.react";
|
import QRCode from "qrcode.react";
|
||||||
|
import { string, any } from "prop-types";
|
||||||
|
|
||||||
function xor(a, b) {
|
function xor(a, b) {
|
||||||
var res = "";
|
var res = "";
|
||||||
@ -14,15 +15,113 @@ function xor(a, b) {
|
|||||||
|
|
||||||
function calculateQrString(serverUrl, username, password) {
|
function calculateQrString(serverUrl, username, password) {
|
||||||
const magicString = "wo9k5tep252qxsa5yde7366kugy6c01w7oeeya9hrmpf0t7ii7";
|
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
|
urlString = btoa(urlString); // to base64
|
||||||
|
|
||||||
return serverUrl + "/#" + urlString;
|
return serverUrl + "/#" + urlString;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ShowUserPdf = props => {
|
UserPdfPage.propTypes = {
|
||||||
|
classes: any,
|
||||||
|
displayname: string,
|
||||||
|
qrCode: any,
|
||||||
|
serverUrl: string,
|
||||||
|
username: string,
|
||||||
|
password: string,
|
||||||
|
};
|
||||||
|
|
||||||
|
function UserPdfPage({
|
||||||
|
classes,
|
||||||
|
displayname,
|
||||||
|
qrCode,
|
||||||
|
serverUrl,
|
||||||
|
username,
|
||||||
|
password,
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<div className={classes.page}>
|
||||||
|
<div className={classes.header}>
|
||||||
|
<div className={classes.name}>{displayname}</div>
|
||||||
|
<img className={classes.logo} alt="Logo" src="images/logo.png" />
|
||||||
|
</div>
|
||||||
|
<div className={classes.body}>
|
||||||
|
<table>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td width="200px">
|
||||||
|
<div className={classes.code_note}>
|
||||||
|
Ihr persönlicher Anmeldecode:
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td className={classes.table_cell}>
|
||||||
|
<div className={classes.credentials_note}>
|
||||||
|
Ihre persönlichen Zugangsdaten:
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<div className={classes.qr}>{qrCode}</div>
|
||||||
|
</td>
|
||||||
|
<td className={classes.table_cell}>
|
||||||
|
<div className={classes.credentials_text}>
|
||||||
|
<br />
|
||||||
|
<table>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>Heimserver:</td>
|
||||||
|
<td>
|
||||||
|
<span className={classes.credentials}>
|
||||||
|
{serverUrl}
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Benutzername:</td>
|
||||||
|
<td>
|
||||||
|
<span className={classes.credentials}>
|
||||||
|
{username}
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Passwort:</td>
|
||||||
|
<td>
|
||||||
|
<span className={classes.credentials}>
|
||||||
|
{password}
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<div className={classes.note}>
|
||||||
|
Hier können Sie Ihre selbst gewählte Schlüsselsicherungs-Passphrase
|
||||||
|
notieren:
|
||||||
|
<br />
|
||||||
|
<br />
|
||||||
|
<br />
|
||||||
|
<hr />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const useStyles = makeStyles(theme => ({
|
const useStyles = makeStyles(theme => ({
|
||||||
page: {
|
page: {
|
||||||
height: 800,
|
height: 800,
|
||||||
@ -87,36 +186,33 @@ const ShowUserPdf = props => {
|
|||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
const ShowUserPdf = props => {
|
||||||
const classes = useStyles();
|
const classes = useStyles();
|
||||||
|
const userPdf = useRef(null);
|
||||||
var resume;
|
|
||||||
|
|
||||||
const exportPDF = () => {
|
const exportPDF = () => {
|
||||||
resume.save();
|
userPdf.current.save();
|
||||||
};
|
};
|
||||||
|
|
||||||
var qrCode = "";
|
let userRecords;
|
||||||
var displayname = "";
|
|
||||||
var id = "";
|
if (props.records) {
|
||||||
var password = "";
|
userRecords = props.records;
|
||||||
var username = "";
|
}
|
||||||
var serverUrl = "";
|
|
||||||
|
|
||||||
if (
|
if (
|
||||||
|
props.location &&
|
||||||
props.location.state &&
|
props.location.state &&
|
||||||
props.location.state.id &&
|
props.location.state.id &&
|
||||||
props.location.state.password
|
props.location.state.password
|
||||||
) {
|
) {
|
||||||
id = props.location.state.id;
|
userRecords = [
|
||||||
password = props.location.state.password;
|
{
|
||||||
|
id: props.location.state.id,
|
||||||
username = id.substring(1, id.indexOf(":"));
|
password: props.location.state.password,
|
||||||
serverUrl = "https://" + id.substring(id.indexOf(":") + 1);
|
displayname: props.location.state.displayname,
|
||||||
|
},
|
||||||
const qrString = calculateQrString(serverUrl, username, password);
|
];
|
||||||
|
|
||||||
qrCode = <QRCode value={qrString} size={128} />;
|
|
||||||
displayname = props.location.state.displayname;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -126,82 +222,39 @@ const ShowUserPdf = props => {
|
|||||||
|
|
||||||
<PDFExport
|
<PDFExport
|
||||||
paperSize={"A4"}
|
paperSize={"A4"}
|
||||||
fileName="User.pdf"
|
fileName="Users.pdf"
|
||||||
title=""
|
title=""
|
||||||
subject=""
|
subject=""
|
||||||
keywords=""
|
keywords=""
|
||||||
ref={r => (resume = r)}
|
ref={userPdf}
|
||||||
|
//ref={r => (resume = r)}
|
||||||
>
|
>
|
||||||
<div className={classes.page}>
|
{userRecords.map(record => {
|
||||||
<div className={classes.header}>
|
if (record.id && record.password) {
|
||||||
<div className={classes.name}>{displayname}</div>
|
const username = record.id.substring(1, record.id.indexOf(":"));
|
||||||
<img className={classes.logo} alt="Logo" src="images/logo.png" />
|
const serverUrl =
|
||||||
</div>
|
"https://" + record.id.substring(record.id.indexOf(":") + 1);
|
||||||
<div className={classes.body}>
|
const qrString = calculateQrString(
|
||||||
<table>
|
serverUrl,
|
||||||
<tbody>
|
username,
|
||||||
<tr>
|
record.password
|
||||||
<td width="200px">
|
);
|
||||||
<div className={classes.code_note}>
|
const qrCode = <QRCode value={qrString} size={128} />;
|
||||||
Ihr persönlicher Anmeldecode:
|
return (
|
||||||
</div>
|
<UserPdfPage
|
||||||
</td>
|
classes={classes}
|
||||||
<td className={classes.table_cell}>
|
displayname={record.displayname}
|
||||||
<div className={classes.credentials_note}>
|
qrCode={qrCode}
|
||||||
Ihre persönlichen Zugangsdaten:
|
serverUrl={serverUrl}
|
||||||
</div>
|
username={username}
|
||||||
</td>
|
password={record.password}
|
||||||
</tr>
|
/>
|
||||||
<tr>
|
);
|
||||||
<td>
|
} else {
|
||||||
<div className={classes.qr}>{qrCode}</div>
|
/* Skip empty PDF pages */
|
||||||
</td>
|
return null;
|
||||||
<td className={classes.table_cell}>
|
}
|
||||||
<div className={classes.credentials_text}>
|
})}
|
||||||
<br />
|
|
||||||
<table>
|
|
||||||
<tbody>
|
|
||||||
<tr>
|
|
||||||
<td>Heimserver:</td>
|
|
||||||
<td>
|
|
||||||
<span className={classes.credentials}>
|
|
||||||
{serverUrl}
|
|
||||||
</span>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>Benutzername:</td>
|
|
||||||
<td>
|
|
||||||
<span className={classes.credentials}>
|
|
||||||
{username}
|
|
||||||
</span>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>Passwort:</td>
|
|
||||||
<td>
|
|
||||||
<span className={classes.credentials}>
|
|
||||||
{password}
|
|
||||||
</span>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
<div className={classes.note}>
|
|
||||||
Hier können Sie Ihre selbst gewählte
|
|
||||||
Schlüsselsicherungs-Passphrase notieren:
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
<hr />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</PDFExport>
|
</PDFExport>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -12,10 +12,12 @@ import {
|
|||||||
ArrayInput,
|
ArrayInput,
|
||||||
ArrayField,
|
ArrayField,
|
||||||
Button,
|
Button,
|
||||||
|
CreateButton,
|
||||||
Datagrid,
|
Datagrid,
|
||||||
DateField,
|
DateField,
|
||||||
Create,
|
Create,
|
||||||
Edit,
|
Edit,
|
||||||
|
ExportButton,
|
||||||
List,
|
List,
|
||||||
Filter,
|
Filter,
|
||||||
Toolbar,
|
Toolbar,
|
||||||
@ -37,11 +39,8 @@ import {
|
|||||||
DeleteButton,
|
DeleteButton,
|
||||||
SaveButton,
|
SaveButton,
|
||||||
regex,
|
regex,
|
||||||
useRedirect,
|
|
||||||
useTranslate,
|
useTranslate,
|
||||||
Pagination,
|
Pagination,
|
||||||
CreateButton,
|
|
||||||
ExportButton,
|
|
||||||
TopToolbar,
|
TopToolbar,
|
||||||
sanitizeListRestProps,
|
sanitizeListRestProps,
|
||||||
NumberField,
|
NumberField,
|
||||||
@ -50,6 +49,13 @@ import SaveQrButton from "./SaveQrButton";
|
|||||||
import { ServerNoticeButton, ServerNoticeBulkButton } from "./ServerNotices";
|
import { ServerNoticeButton, ServerNoticeBulkButton } from "./ServerNotices";
|
||||||
import { DeviceRemoveButton } from "./devices";
|
import { DeviceRemoveButton } from "./devices";
|
||||||
import { makeStyles } from "@material-ui/core/styles";
|
import { makeStyles } from "@material-ui/core/styles";
|
||||||
|
import { Link } from "react-router-dom";
|
||||||
|
|
||||||
|
const redirect = (basePath, id, data) => {
|
||||||
|
return {
|
||||||
|
pathname: "/importcsv",
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
const useStyles = makeStyles({
|
const useStyles = makeStyles({
|
||||||
small: {
|
small: {
|
||||||
@ -81,7 +87,6 @@ const UserListActions = ({
|
|||||||
total,
|
total,
|
||||||
...rest
|
...rest
|
||||||
}) => {
|
}) => {
|
||||||
const redirectTo = useRedirect();
|
|
||||||
return (
|
return (
|
||||||
<TopToolbar className={className} {...sanitizeListRestProps(rest)}>
|
<TopToolbar className={className} {...sanitizeListRestProps(rest)}>
|
||||||
{filters &&
|
{filters &&
|
||||||
@ -101,6 +106,10 @@ const UserListActions = ({
|
|||||||
exporter={exporter}
|
exporter={exporter}
|
||||||
maxResults={maxResults}
|
maxResults={maxResults}
|
||||||
/>
|
/>
|
||||||
|
{/* Add your custom actions */}
|
||||||
|
<Button component={Link} to={redirect} label="CSV Import">
|
||||||
|
<GetAppIcon style={{ transform: "rotate(180deg)", fontSize: "20" }} />
|
||||||
|
</Button>
|
||||||
</TopToolbar>
|
</TopToolbar>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@ -178,7 +187,7 @@ export const UserList = props => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// redirect to the related Author show page
|
// redirect to the related Author show page
|
||||||
const redirect = (basePath, id, data) => {
|
const redirectToPdf = (basePath, id, data) => {
|
||||||
return {
|
return {
|
||||||
pathname: "/showpdf",
|
pathname: "/showpdf",
|
||||||
state: {
|
state: {
|
||||||
@ -193,7 +202,7 @@ const UserCreateToolbar = props => (
|
|||||||
<Toolbar {...props}>
|
<Toolbar {...props}>
|
||||||
<SaveQrButton
|
<SaveQrButton
|
||||||
label="synapseadmin.action.save_and_show"
|
label="synapseadmin.action.save_and_show"
|
||||||
redirect={redirect}
|
redirect={redirectToPdf}
|
||||||
submitOnEnter={true}
|
submitOnEnter={true}
|
||||||
/>
|
/>
|
||||||
<SaveButton
|
<SaveButton
|
||||||
|
@ -104,6 +104,8 @@ export default {
|
|||||||
with_error:
|
with_error:
|
||||||
"%{smart_count} Eintrag mit Fehlern ||| %{smart_count} Einträge mit Fehlern",
|
"%{smart_count} Eintrag mit Fehlern ||| %{smart_count} Einträge mit Fehlern",
|
||||||
simulated_only: "Import-Vorgang war nur simuliert",
|
simulated_only: "Import-Vorgang war nur simuliert",
|
||||||
|
for_print:
|
||||||
|
"%{smart_count} Eintrag zum Drucken verfügbar |||| %{smart_count} Einträge zum Drucken verfügbar",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -104,6 +104,8 @@ export default {
|
|||||||
with_error:
|
with_error:
|
||||||
"%{smart_count} entry with errors ||| %{smart_count} entries with errors",
|
"%{smart_count} entry with errors ||| %{smart_count} entries with errors",
|
||||||
simulated_only: "Run was only simulated",
|
simulated_only: "Run was only simulated",
|
||||||
|
for_print:
|
||||||
|
"%{smart_count} entry available for printing |||| %{smart_count} entries available for printing",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -25,9 +25,6 @@ const mxcUrlToHttp = mxcUrl => {
|
|||||||
return `${homeserver}/_matrix/media/r0/thumbnail/${serverName}/${mediaId}?width=24&height=24&method=scale`;
|
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 = {
|
const POWER_LEVELS = {
|
||||||
admin: 100,
|
admin: 100,
|
||||||
mod: 50,
|
mod: 50,
|
||||||
|
Loading…
Reference in New Issue
Block a user