Merge branch 'master' into zh-cn
# Conflicts: # yarn.lock
This commit is contained in:
commit
bbfa39e90a
21
README.md
21
README.md
@ -35,7 +35,26 @@ Steps for 1):
|
|||||||
|
|
||||||
Steps for 2):
|
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
|
- browse to http://localhost:8080
|
||||||
|
|
||||||
## Screenshots
|
## Screenshots
|
||||||
|
21
docker-compose.yml
Normal file
21
docker-compose.yml
Normal 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
|
145
src/components/media.js
Normal file
145
src/components/media.js
Normal 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>
|
||||||
|
);
|
||||||
|
};
|
@ -1,13 +1,51 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
|
import { cloneElement } from "react";
|
||||||
import {
|
import {
|
||||||
Datagrid,
|
Datagrid,
|
||||||
|
ExportButton,
|
||||||
Filter,
|
Filter,
|
||||||
List,
|
List,
|
||||||
NumberField,
|
NumberField,
|
||||||
TextField,
|
|
||||||
SearchInput,
|
|
||||||
Pagination,
|
Pagination,
|
||||||
|
sanitizeListRestProps,
|
||||||
|
SearchInput,
|
||||||
|
TextField,
|
||||||
|
TopToolbar,
|
||||||
|
useListContext,
|
||||||
} from "react-admin";
|
} 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 => (
|
const UserMediaStatsPagination = props => (
|
||||||
<Pagination {...props} rowsPerPageOptions={[10, 25, 50, 100, 500, 1000]} />
|
<Pagination {...props} rowsPerPageOptions={[10, 25, 50, 100, 500, 1000]} />
|
||||||
@ -23,6 +61,7 @@ export const UserMediaStatsList = props => {
|
|||||||
return (
|
return (
|
||||||
<List
|
<List
|
||||||
{...props}
|
{...props}
|
||||||
|
actions={<ListActions />}
|
||||||
filters={<UserMediaStatsFilter />}
|
filters={<UserMediaStatsFilter />}
|
||||||
pagination={<UserMediaStatsPagination />}
|
pagination={<UserMediaStatsPagination />}
|
||||||
sort={{ field: "media_length", order: "DESC" }}
|
sort={{ field: "media_length", order: "DESC" }}
|
||||||
|
@ -236,6 +236,23 @@ export default {
|
|||||||
last_access_ts: "Letzter Zugriff",
|
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: {
|
pushers: {
|
||||||
name: "Pusher |||| Pushers",
|
name: "Pusher |||| Pushers",
|
||||||
fields: {
|
fields: {
|
||||||
|
@ -234,6 +234,23 @@ export default {
|
|||||||
last_access_ts: "Last access",
|
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: {
|
pushers: {
|
||||||
name: "Pusher |||| Pushers",
|
name: "Pusher |||| Pushers",
|
||||||
fields: {
|
fields: {
|
||||||
|
@ -160,6 +160,16 @@ const resourceMap = {
|
|||||||
)}/${params.id}`,
|
)}/${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: {
|
servernotices: {
|
||||||
map: n => ({ id: n.event_id }),
|
map: n => ({ id: n.event_id }),
|
||||||
create: data => ({
|
create: data => ({
|
||||||
|
Loading…
Reference in New Issue
Block a user