Merge branch 'master' into room_dir

This commit is contained in:
Dirk Klimpel 2021-04-21 11:15:53 +02:00 committed by GitHub
commit 84004124f1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 1896 additions and 1478 deletions

View File

@ -35,7 +35,26 @@ Steps for 1):
Steps for 2):
- run the Docker container: `docker run -p 8080:80 awesometechnologies/synapse-admin`
- run the Docker container from the public docker registry: `docker run -p 8080:80 awesometechnologies/synapse-admin` or use the (docker-compose.yml)[docker-compose.yml]: `docker-compose up -d`
> note: if you're building on an architecture other than amd64 (for example a raspberry pi), make sure to define a maximum ram for node. otherwise the build will fail.
```yml
version: "3"
services:
synapse-admin:
container_name: synapse-admin
hostname: synapse-admin
build:
context: https://github.com/Awesome-Technologies/synapse-admin.git
# args:
# - NODE_OPTIONS="--max_old_space_size=1024"
ports:
- "8080:80"
restart: unless-stopped
```
- browse to http://localhost:8080
## Screenshots

21
docker-compose.yml Normal file
View File

@ -0,0 +1,21 @@
version: "3"
services:
synapse-admin:
container_name: synapse-admin
hostname: synapse-admin
image: awesometechnologies/synapse-admin:latest
# build:
# context: .
# to use the docker-compose as standalone without a local repo clone,
# replace the context definition with this:
# context: https://github.com/Awesome-Technologies/synapse-admin.git
# if you're building on an architecture other than amd64, make sure
# to define a maximum ram for node. otherwise the build will fail.
# args:
# - NODE_OPTIONS="--max_old_space_size=1024"
ports:
- "8080:80"
restart: unless-stopped

View File

@ -19,14 +19,15 @@
"eslint-config-prettier": "^6.10.1",
"eslint-plugin-prettier": "^3.1.2",
"jest-fetch-mock": "^3.0.3",
"prettier": "^2.0.0"
"prettier": "^2.0.0",
"ra-test": "^3.14.0"
},
"dependencies": {
"papaparse": "^5.2.0",
"prop-types": "^15.7.2",
"ra-language-german": "^2.1.2",
"react": "^16.13.1",
"react-admin": "^3.12.2",
"react": "^17.0.0",
"react-admin": "^3.14.0",
"react-dom": "^16.14.0",
"react-scripts": "^3.4.4"
},

View File

@ -31,6 +31,7 @@ const i18nProvider = polyglotI18nProvider(
const App = () => (
<Admin
disableTelemetry
loginPage={LoginPage}
authProvider={authProvider}
dataProvider={dataProvider}

View File

@ -1,5 +1,5 @@
import React from "react";
import { TestContext } from "react-admin";
import { TestContext } from "ra-test";
import { shallow } from "enzyme";
import App from "./App";

View File

@ -1,5 +1,5 @@
import React from "react";
import { TestContext } from "react-admin";
import { TestContext } from "ra-test";
import { shallow } from "enzyme";
import LoginPage from "./LoginPage";

145
src/components/media.js Normal file
View File

@ -0,0 +1,145 @@
import React, { Fragment, useState } from "react";
import classnames from "classnames";
import { fade } from "@material-ui/core/styles/colorManipulator";
import { makeStyles } from "@material-ui/core/styles";
import {
BooleanInput,
Button,
DateTimeInput,
NumberInput,
SaveButton,
SimpleForm,
Toolbar,
useDelete,
useNotify,
useTranslate,
} from "react-admin";
import IconCancel from "@material-ui/icons/Cancel";
import Dialog from "@material-ui/core/Dialog";
import DialogContent from "@material-ui/core/DialogContent";
import DialogContentText from "@material-ui/core/DialogContentText";
import DialogTitle from "@material-ui/core/DialogTitle";
import DeleteSweepIcon from "@material-ui/icons/DeleteSweep";
const useStyles = makeStyles(
theme => ({
deleteButton: {
color: theme.palette.error.main,
"&:hover": {
backgroundColor: fade(theme.palette.error.main, 0.12),
// Reset on mouse devices
"@media (hover: none)": {
backgroundColor: "transparent",
},
},
},
}),
{ name: "RaDeleteDeviceButton" }
);
const DeleteMediaDialog = ({ open, loading, onClose, onSend }) => {
const translate = useTranslate();
const dateParser = v => {
const d = new Date(v);
if (isNaN(d)) return 0;
return d.getTime();
};
const DeleteMediaToolbar = props => {
return (
<Toolbar {...props}>
<SaveButton
label="resources.delete_media.action.send"
icon={<DeleteSweepIcon />}
/>
<Button label="ra.action.cancel" onClick={onClose}>
<IconCancel />
</Button>
</Toolbar>
);
};
return (
<Dialog open={open} onClose={onClose} loading={loading}>
<DialogTitle>
{translate("resources.delete_media.action.send")}
</DialogTitle>
<DialogContent>
<DialogContentText>
{translate("resources.delete_media.helper.send")}
</DialogContentText>
<SimpleForm
toolbar={<DeleteMediaToolbar />}
submitOnEnter={false}
redirect={false}
save={onSend}
>
<DateTimeInput
fullWidth
source="before_ts"
label="resources.delete_media.fields.before_ts"
defaultValue={0}
parse={dateParser}
/>
<NumberInput
fullWidth
source="size_gt"
label="resources.delete_media.fields.size_gt"
defaultValue={0}
min={0}
step={1024}
/>
<BooleanInput
fullWidth
source="keep_profiles"
label="resources.delete_media.fields.keep_profiles"
defaultValue={true}
/>
</SimpleForm>
</DialogContent>
</Dialog>
);
};
export const DeleteMediaButton = props => {
const classes = useStyles(props);
const [open, setOpen] = useState(false);
const notify = useNotify();
const [deleteOne, { loading }] = useDelete("delete_media");
const handleDialogOpen = () => setOpen(true);
const handleDialogClose = () => setOpen(false);
const handleSend = values => {
deleteOne(
{ payload: { ...values } },
{
onSuccess: () => {
notify("resources.delete_media.action.send_success");
handleDialogClose();
},
onFailure: () =>
notify("resources.delete_media.action.send_failure", "error"),
}
);
};
return (
<Fragment>
<Button
label="resources.delete_media.action.send"
onClick={handleDialogOpen}
disabled={loading}
className={classnames("ra-delete-button", classes.deleteButton)}
>
<DeleteSweepIcon />
</Button>
<DeleteMediaDialog
open={open}
onClose={handleDialogClose}
onSend={handleSend}
/>
</Fragment>
);
};

View File

@ -1,13 +1,51 @@
import React from "react";
import { cloneElement } from "react";
import {
Datagrid,
ExportButton,
Filter,
List,
NumberField,
TextField,
SearchInput,
Pagination,
sanitizeListRestProps,
SearchInput,
TextField,
TopToolbar,
useListContext,
} from "react-admin";
import { DeleteMediaButton } from "./media";
const ListActions = props => {
const { className, exporter, filters, maxResults, ...rest } = props;
const {
currentSort,
resource,
displayedFilters,
filterValues,
showFilter,
total,
} = useListContext();
return (
<TopToolbar className={className} {...sanitizeListRestProps(rest)}>
{filters &&
cloneElement(filters, {
resource,
showFilter,
displayedFilters,
filterValues,
context: "button",
})}
<DeleteMediaButton />
<ExportButton
disabled={total === 0}
resource={resource}
sort={currentSort}
filterValues={filterValues}
maxResults={maxResults}
/>
</TopToolbar>
);
};
const UserMediaStatsPagination = props => (
<Pagination {...props} rowsPerPageOptions={[10, 25, 50, 100, 500, 1000]} />
@ -23,6 +61,7 @@ export const UserMediaStatsList = props => {
return (
<List
{...props}
actions={<ListActions />}
filters={<UserMediaStatsFilter />}
pagination={<UserMediaStatsPagination />}
sort={{ field: "media_length", order: "DESC" }}

View File

@ -240,6 +240,23 @@ export default {
last_access_ts: "Letzter Zugriff",
},
},
delete_media: {
name: "Medien",
fields: {
before_ts: "Letzter Zugriff vor",
size_gt: "Größer als (in Bytes)",
keep_profiles: "Behalte Profilbilder",
},
action: {
send: "Medien löschen",
send_success: "Anfrage erfolgreich versendet.",
send_failure: "Beim Versenden ist ein Fehler aufgetreten.",
},
helper: {
send:
"Diese API löscht die lokalen Medien von der Festplatte des eigenen Servers. Dies umfasst alle lokalen Miniaturbilder und Kopien von Medien. Diese API wirkt sich nicht auf Medien aus, die sich in externen Medien-Repositories befinden.",
},
},
pushers: {
name: "Pusher |||| Pushers",
fields: {
@ -297,6 +314,10 @@ export default {
},
ra: {
...germanMessages.ra,
action: {
...germanMessages.ra.action,
unselect: "Abwählen",
},
auth: {
...germanMessages.ra.auth,
auth_check_error: "Anmeldung fehlgeschlagen",

View File

@ -6,6 +6,7 @@ export default {
auth: {
base_url: "Homeserver URL",
welcome: "Welcome to Synapse-admin",
server_version: "Synapse version",
username_error: "Please enter fully qualified user ID: '@user:domain'",
protocol_error: "URL has to start with 'http://' or 'https://'",
url_error: "Not a valid Matrix server URL",
@ -134,7 +135,7 @@ export default {
name: "Name",
canonical_alias: "Alias",
joined_members: "Members",
joined_local_members: "local members",
joined_local_members: "Local members",
state_events: "State events",
version: "Version",
is_encrypted: "Encrypted",
@ -235,6 +236,23 @@ export default {
last_access_ts: "Last access",
},
},
delete_media: {
name: "Media",
fields: {
before_ts: "last access before",
size_gt: "Larger then (in bytes)",
keep_profiles: "Keep profile images",
},
action: {
send: "Delete media",
send_success: "Request successfully sent.",
send_failure: "An error has occurred.",
},
helper: {
send:
"This API deletes the local media from the disk of your own server. This includes any local thumbnails and copies of media downloaded. This API will not affect media that has been uploaded to external media repositories.",
},
},
pushers: {
name: "Pusher |||| Pushers",
fields: {

View File

@ -10,6 +10,7 @@ const authProvider = {
type: "m.login.password",
user: username,
password: password,
initial_device_display_name: "Synapse Admin",
}),
};
@ -30,8 +31,26 @@ const authProvider = {
},
// called when the user clicks on the logout button
logout: () => {
console.log("logout ");
localStorage.removeItem("access_token");
console.log("logout");
const logout_api_url =
localStorage.getItem("base_url") + "/_matrix/client/r0/logout";
const access_token = localStorage.getItem("access_token");
const options = {
method: "POST",
user: {
authenticated: true,
token: `Bearer ${access_token}`,
},
};
if (typeof access_token === "string") {
fetchUtils.fetchJson(logout_api_url, options).then(({ json }) => {
localStorage.removeItem("access_token");
localStorage.removeItem("device_id");
});
}
return Promise.resolve();
},
// called when the API returns an error
@ -46,7 +65,7 @@ const authProvider = {
checkAuth: () => {
const access_token = localStorage.getItem("access_token");
console.log("checkAuth " + access_token);
return typeof access_token == "string"
return typeof access_token === "string"
? Promise.resolve()
: Promise.reject();
},

View File

@ -160,6 +160,16 @@ const resourceMap = {
)}/${params.id}`,
}),
},
delete_media: {
delete: params => ({
endpoint: `/_synapse/admin/v1/media/${localStorage.getItem(
"home_server"
)}/delete?before_ts=${params.before_ts}&size_gt=${
params.size_gt
}&keep_profiles=${params.keep_profiles}`,
method: "POST",
}),
},
servernotices: {
map: n => ({ id: n.event_id }),
create: data => ({

3056
yarn.lock

File diff suppressed because it is too large Load Diff