Merge branch 'master' into user_erased_details
This commit is contained in:
		
						commit
						80135753a4
					
				
							
								
								
									
										2
									
								
								.github/workflows/edge_ghpage.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/workflows/edge_ghpage.yml
									
									
									
									
										vendored
									
									
								
							@ -20,7 +20,7 @@ jobs:
 | 
			
		||||
          yarn build
 | 
			
		||||
 | 
			
		||||
      - name: Deploy 🚀
 | 
			
		||||
        uses: JamesIves/github-pages-deploy-action@v4.4.3
 | 
			
		||||
        uses: JamesIves/github-pages-deploy-action@v4.5.0
 | 
			
		||||
        with:
 | 
			
		||||
          branch: gh-pages
 | 
			
		||||
          folder: build
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										27
									
								
								package.json
									
									
									
									
									
								
							
							
						
						
									
										27
									
								
								package.json
									
									
									
									
									
								
							@ -10,29 +10,28 @@
 | 
			
		||||
    "url": "https://github.com/Awesome-Technologies/synapse-admin"
 | 
			
		||||
  },
 | 
			
		||||
  "devDependencies": {
 | 
			
		||||
    "@testing-library/jest-dom": "^5.16.5",
 | 
			
		||||
    "@testing-library/react": "^12.1.5",
 | 
			
		||||
    "@testing-library/user-event": "^14.4.3",
 | 
			
		||||
    "eslint": "^8.55.0",
 | 
			
		||||
    "eslint-config-prettier": "^9.0.0",
 | 
			
		||||
    "@testing-library/jest-dom": "^6.0.0",
 | 
			
		||||
    "@testing-library/react": "^14.0.0",
 | 
			
		||||
    "@testing-library/user-event": "^14.5.2",
 | 
			
		||||
    "eslint": "^8.56.0",
 | 
			
		||||
    "eslint-config-prettier": "^9.1.0",
 | 
			
		||||
    "eslint-config-react-app": "^7.0.1",
 | 
			
		||||
    "eslint-plugin-prettier": "^4.2.1",
 | 
			
		||||
    "eslint-plugin-prettier": "^5.1.3",
 | 
			
		||||
    "jest-fetch-mock": "^3.0.3",
 | 
			
		||||
    "prettier": "^2.2.0"
 | 
			
		||||
    "prettier": "^3.2.5"
 | 
			
		||||
  },
 | 
			
		||||
  "dependencies": {
 | 
			
		||||
    "@mui/icons-material": "^5.14.19",
 | 
			
		||||
    "@mui/material": "^5.14.8",
 | 
			
		||||
    "@mui/styles": "5.14.10",
 | 
			
		||||
    "@mui/icons-material": "^5.15.7",
 | 
			
		||||
    "@mui/material": "^5.15.7",
 | 
			
		||||
    "@mui/styles": "^5.15.8",
 | 
			
		||||
    "papaparse": "^5.4.1",
 | 
			
		||||
    "prop-types": "^15.8.1",
 | 
			
		||||
    "ra-language-chinese": "^2.0.10",
 | 
			
		||||
    "ra-language-french": "^4.16.2",
 | 
			
		||||
    "ra-language-french": "^4.16.9",
 | 
			
		||||
    "ra-language-german": "^3.13.4",
 | 
			
		||||
    "ra-language-italian": "^3.13.1",
 | 
			
		||||
    "react": "^17.0.0",
 | 
			
		||||
    "react": "^18.0.0",
 | 
			
		||||
    "react-admin": "^4.16.9",
 | 
			
		||||
    "react-dom": "^17.0.2",
 | 
			
		||||
    "react-dom": "^18.0.0",
 | 
			
		||||
    "react-scripts": "^5.0.1"
 | 
			
		||||
  },
 | 
			
		||||
  "scripts": {
 | 
			
		||||
 | 
			
		||||
@ -40,6 +40,7 @@ const i18nProvider = polyglotI18nProvider(
 | 
			
		||||
const App = () => (
 | 
			
		||||
  <Admin
 | 
			
		||||
    disableTelemetry
 | 
			
		||||
    requireAuth
 | 
			
		||||
    loginPage={LoginPage}
 | 
			
		||||
    authProvider={authProvider}
 | 
			
		||||
    dataProvider={dataProvider}
 | 
			
		||||
@ -6,7 +6,17 @@ import { useRecordContext } from "react-admin";
 | 
			
		||||
const AvatarField = ({ source, ...rest }) => {
 | 
			
		||||
  const record = useRecordContext(rest);
 | 
			
		||||
  const src = get(record, source)?.toString();
 | 
			
		||||
  return <Avatar src={src} {...rest} />;
 | 
			
		||||
  const { alt, classes, sizes, sx, variant } = rest;
 | 
			
		||||
  return (
 | 
			
		||||
    <Avatar
 | 
			
		||||
      alt={alt}
 | 
			
		||||
      classes={classes}
 | 
			
		||||
      sizes={sizes}
 | 
			
		||||
      src={src}
 | 
			
		||||
      sx={sx}
 | 
			
		||||
      variant={variant}
 | 
			
		||||
    />
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default AvatarField;
 | 
			
		||||
							
								
								
									
										18
									
								
								src/components/AvatarField.test.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								src/components/AvatarField.test.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,18 @@
 | 
			
		||||
import React from "react";
 | 
			
		||||
import { RecordContextProvider } from "react-admin";
 | 
			
		||||
import { render, screen } from "@testing-library/react";
 | 
			
		||||
import AvatarField from "./AvatarField";
 | 
			
		||||
 | 
			
		||||
describe("AvatarField", () => {
 | 
			
		||||
  it("shows image", () => {
 | 
			
		||||
    const value = {
 | 
			
		||||
      avatar: "foo",
 | 
			
		||||
    };
 | 
			
		||||
    render(
 | 
			
		||||
      <RecordContextProvider value={value}>
 | 
			
		||||
        <AvatarField source="avatar" />
 | 
			
		||||
      </RecordContextProvider>
 | 
			
		||||
    );
 | 
			
		||||
    expect(screen.getByRole("img").getAttribute("src")).toBe("foo");
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
@ -2,6 +2,7 @@ import React from "react";
 | 
			
		||||
import {
 | 
			
		||||
  Datagrid,
 | 
			
		||||
  DateField,
 | 
			
		||||
  DeleteButton,
 | 
			
		||||
  List,
 | 
			
		||||
  NumberField,
 | 
			
		||||
  Pagination,
 | 
			
		||||
@ -10,6 +11,8 @@ import {
 | 
			
		||||
  Tab,
 | 
			
		||||
  TabbedShowLayout,
 | 
			
		||||
  TextField,
 | 
			
		||||
  TopToolbar,
 | 
			
		||||
  useRecordContext,
 | 
			
		||||
  useTranslate,
 | 
			
		||||
} from "react-admin";
 | 
			
		||||
import PageviewIcon from "@mui/icons-material/Pageview";
 | 
			
		||||
@ -32,7 +35,7 @@ const ReportPagination = () => (
 | 
			
		||||
export const ReportShow = props => {
 | 
			
		||||
  const translate = useTranslate();
 | 
			
		||||
  return (
 | 
			
		||||
    <Show {...props}>
 | 
			
		||||
    <Show {...props} actions={<ReportShowActions />}>
 | 
			
		||||
      <TabbedShowLayout>
 | 
			
		||||
        <Tab
 | 
			
		||||
          label={translate("synapseadmin.reports.tabs.basic", {
 | 
			
		||||
@ -99,6 +102,21 @@ export const ReportShow = props => {
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const ReportShowActions = () => {
 | 
			
		||||
  const record = useRecordContext();
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <TopToolbar>
 | 
			
		||||
      <DeleteButton
 | 
			
		||||
        record={record}
 | 
			
		||||
        mutationMode="pessimistic"
 | 
			
		||||
        confirmTitle="resources.reports.action.erase.title"
 | 
			
		||||
        confirmContent="resources.reports.action.erase.content"
 | 
			
		||||
      />
 | 
			
		||||
    </TopToolbar>
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const ReportList = props => (
 | 
			
		||||
  <List
 | 
			
		||||
    {...props}
 | 
			
		||||
@ -1,6 +1,5 @@
 | 
			
		||||
import React, { useState, useEffect } from "react";
 | 
			
		||||
import {
 | 
			
		||||
  fetchUtils,
 | 
			
		||||
  Form,
 | 
			
		||||
  FormDataConsumer,
 | 
			
		||||
  Notification,
 | 
			
		||||
@ -27,6 +26,13 @@ import {
 | 
			
		||||
} from "@mui/material";
 | 
			
		||||
import { styled } from "@mui/material/styles";
 | 
			
		||||
import LockIcon from "@mui/icons-material/Lock";
 | 
			
		||||
import {
 | 
			
		||||
  getServerVersion,
 | 
			
		||||
  getSupportedLoginFlows,
 | 
			
		||||
  getWellKnownUrl,
 | 
			
		||||
  isValidBaseUrl,
 | 
			
		||||
  splitMxid,
 | 
			
		||||
} from "../synapse/synapse";
 | 
			
		||||
 | 
			
		||||
const FormBox = styled(Box)(({ theme }) => ({
 | 
			
		||||
  display: "flex",
 | 
			
		||||
@ -113,8 +119,8 @@ const LoginPage = () => {
 | 
			
		||||
          typeof error === "string"
 | 
			
		||||
            ? error
 | 
			
		||||
            : typeof error === "undefined" || !error.message
 | 
			
		||||
            ? "ra.auth.sign_in_error"
 | 
			
		||||
            : error.message
 | 
			
		||||
              ? "ra.auth.sign_in_error"
 | 
			
		||||
              : error.message
 | 
			
		||||
        );
 | 
			
		||||
        console.error(error);
 | 
			
		||||
      });
 | 
			
		||||
@ -155,8 +161,8 @@ const LoginPage = () => {
 | 
			
		||||
        typeof error === "string"
 | 
			
		||||
          ? error
 | 
			
		||||
          : typeof error === "undefined" || !error.message
 | 
			
		||||
          ? "ra.auth.sign_in_error"
 | 
			
		||||
          : error.message,
 | 
			
		||||
            ? "ra.auth.sign_in_error"
 | 
			
		||||
            : error.message,
 | 
			
		||||
        { type: "warning" }
 | 
			
		||||
      );
 | 
			
		||||
    });
 | 
			
		||||
@ -170,87 +176,42 @@ const LoginPage = () => {
 | 
			
		||||
    window.location.href = ssoFullUrl;
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  const extractHomeServer = username => {
 | 
			
		||||
    const usernameRegex = /@[a-zA-Z0-9._=\-/]+:([a-zA-Z0-9\-.]+\.[a-zA-Z]+)/;
 | 
			
		||||
    if (!username) return null;
 | 
			
		||||
    const res = username.match(usernameRegex);
 | 
			
		||||
    if (res) return res[1];
 | 
			
		||||
    return null;
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  const UserData = ({ formData }) => {
 | 
			
		||||
    const form = useFormContext();
 | 
			
		||||
    const [serverVersion, setServerVersion] = useState("");
 | 
			
		||||
 | 
			
		||||
    const handleUsernameChange = _ => {
 | 
			
		||||
      if (formData.base_url || cfg_base_url) return;
 | 
			
		||||
      // check if username is a full qualified userId then set base_url accordially
 | 
			
		||||
      const home_server = extractHomeServer(formData.username);
 | 
			
		||||
      const wellKnownUrl = `https://${home_server}/.well-known/matrix/client`;
 | 
			
		||||
      if (home_server) {
 | 
			
		||||
        // fetch .well-known entry to get base_url
 | 
			
		||||
        fetchUtils
 | 
			
		||||
          .fetchJson(wellKnownUrl, { method: "GET" })
 | 
			
		||||
          .then(({ json }) => {
 | 
			
		||||
            form.setValue("base_url", json["m.homeserver"].base_url);
 | 
			
		||||
          })
 | 
			
		||||
          .catch(_ => {
 | 
			
		||||
            // if there is no .well-known entry, try the home server name
 | 
			
		||||
            form.setValue("base_url", `https://${home_server}`);
 | 
			
		||||
          });
 | 
			
		||||
      // check if username is a full qualified userId then set base_url accordingly
 | 
			
		||||
      const domain = splitMxid(formData.username)?.domain;
 | 
			
		||||
      if (domain) {
 | 
			
		||||
        getWellKnownUrl(domain).then(url => form.setValue("base_url", url));
 | 
			
		||||
      }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    useEffect(
 | 
			
		||||
      _ => {
 | 
			
		||||
        if (
 | 
			
		||||
          !formData.base_url ||
 | 
			
		||||
          !formData.base_url.match(
 | 
			
		||||
            /^(http|https):\/\/[a-zA-Z0-9\-.]+(:\d{1,5})?$/
 | 
			
		||||
    useEffect(() => {
 | 
			
		||||
      if (!isValidBaseUrl(formData.base_url)) return;
 | 
			
		||||
 | 
			
		||||
      getServerVersion(formData.base_url)
 | 
			
		||||
        .then(serverVersion =>
 | 
			
		||||
          setServerVersion(
 | 
			
		||||
            `${translate("synapseadmin.auth.server_version")} ${serverVersion}`
 | 
			
		||||
          )
 | 
			
		||||
        )
 | 
			
		||||
          return;
 | 
			
		||||
        const versionUrl = `${formData.base_url}/_synapse/admin/v1/server_version`;
 | 
			
		||||
        fetchUtils
 | 
			
		||||
          .fetchJson(versionUrl, { method: "GET" })
 | 
			
		||||
          .then(({ json }) => {
 | 
			
		||||
            setServerVersion(
 | 
			
		||||
              `${translate("synapseadmin.auth.server_version")} ${
 | 
			
		||||
                json["server_version"]
 | 
			
		||||
              }`
 | 
			
		||||
            );
 | 
			
		||||
          })
 | 
			
		||||
          .catch(_ => {
 | 
			
		||||
            setServerVersion("");
 | 
			
		||||
          });
 | 
			
		||||
        .catch(() => setServerVersion(""));
 | 
			
		||||
 | 
			
		||||
        // Set SSO Url
 | 
			
		||||
        const authMethodUrl = `${formData.base_url}/_matrix/client/r0/login`;
 | 
			
		||||
        let supportPass = false,
 | 
			
		||||
          supportSSO = false;
 | 
			
		||||
        fetchUtils
 | 
			
		||||
          .fetchJson(authMethodUrl, { method: "GET" })
 | 
			
		||||
          .then(({ json }) => {
 | 
			
		||||
            json.flows.forEach(f => {
 | 
			
		||||
              if (f.type === "m.login.password") {
 | 
			
		||||
                supportPass = true;
 | 
			
		||||
              } else if (f.type === "m.login.sso") {
 | 
			
		||||
                supportSSO = true;
 | 
			
		||||
              }
 | 
			
		||||
            });
 | 
			
		||||
            setSupportPassAuth(supportPass);
 | 
			
		||||
            if (supportSSO) {
 | 
			
		||||
              setSSOBaseUrl(formData.base_url);
 | 
			
		||||
            } else {
 | 
			
		||||
              setSSOBaseUrl("");
 | 
			
		||||
            }
 | 
			
		||||
          })
 | 
			
		||||
          .catch(_ => {
 | 
			
		||||
            setSSOBaseUrl("");
 | 
			
		||||
          });
 | 
			
		||||
      },
 | 
			
		||||
      [formData.base_url]
 | 
			
		||||
    );
 | 
			
		||||
      // Set SSO Url
 | 
			
		||||
      getSupportedLoginFlows(formData.base_url)
 | 
			
		||||
        .then(loginFlows => {
 | 
			
		||||
          const supportPass =
 | 
			
		||||
            loginFlows.find(f => f.type === "m.login.password") !== undefined;
 | 
			
		||||
          const supportSSO =
 | 
			
		||||
            loginFlows.find(f => f.type === "m.login.sso") !== undefined;
 | 
			
		||||
          setSupportPassAuth(supportPass);
 | 
			
		||||
          setSSOBaseUrl(supportSSO ? formData.base_url : "");
 | 
			
		||||
        })
 | 
			
		||||
        .catch(() => setSSOBaseUrl(""));
 | 
			
		||||
    }, [formData.base_url]);
 | 
			
		||||
 | 
			
		||||
    return (
 | 
			
		||||
      <>
 | 
			
		||||
@ -1,78 +0,0 @@
 | 
			
		||||
import React, { useState } from "react";
 | 
			
		||||
import {
 | 
			
		||||
  Button,
 | 
			
		||||
  useDelete,
 | 
			
		||||
  useNotify,
 | 
			
		||||
  Confirm,
 | 
			
		||||
  useRecordContext,
 | 
			
		||||
  useRefresh,
 | 
			
		||||
} from "react-admin";
 | 
			
		||||
import ActionDelete from "@mui/icons-material/Delete";
 | 
			
		||||
import { alpha, useTheme } from "@mui/material/styles";
 | 
			
		||||
 | 
			
		||||
export const DeviceRemoveButton = props => {
 | 
			
		||||
  const theme = useTheme();
 | 
			
		||||
  const record = useRecordContext();
 | 
			
		||||
  const [open, setOpen] = useState(false);
 | 
			
		||||
  const refresh = useRefresh();
 | 
			
		||||
  const notify = useNotify();
 | 
			
		||||
 | 
			
		||||
  const [removeDevice, { isLoading }] = useDelete();
 | 
			
		||||
 | 
			
		||||
  if (!record) return null;
 | 
			
		||||
 | 
			
		||||
  const handleClick = () => setOpen(true);
 | 
			
		||||
  const handleDialogClose = () => setOpen(false);
 | 
			
		||||
 | 
			
		||||
  const handleConfirm = () => {
 | 
			
		||||
    removeDevice(
 | 
			
		||||
      "devices",
 | 
			
		||||
      // needs previousData for user_id
 | 
			
		||||
      { id: record.id, previousData: record },
 | 
			
		||||
      {
 | 
			
		||||
        onSuccess: () => {
 | 
			
		||||
          notify("resources.devices.action.erase.success");
 | 
			
		||||
          refresh();
 | 
			
		||||
        },
 | 
			
		||||
        onError: () => {
 | 
			
		||||
          notify("resources.devices.action.erase.failure", { type: "error" });
 | 
			
		||||
        },
 | 
			
		||||
      }
 | 
			
		||||
    );
 | 
			
		||||
    setOpen(false);
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <>
 | 
			
		||||
      <Button
 | 
			
		||||
        {...props}
 | 
			
		||||
        label="ra.action.remove"
 | 
			
		||||
        onClick={handleClick}
 | 
			
		||||
        sx={{
 | 
			
		||||
          color: theme.palette.error.main,
 | 
			
		||||
          "&:hover": {
 | 
			
		||||
            backgroundColor: alpha(theme.palette.error.main, 0.12),
 | 
			
		||||
            // Reset on mouse devices
 | 
			
		||||
            "@media (hover: none)": {
 | 
			
		||||
              backgroundColor: "transparent",
 | 
			
		||||
            },
 | 
			
		||||
          },
 | 
			
		||||
        }}
 | 
			
		||||
      >
 | 
			
		||||
        <ActionDelete />
 | 
			
		||||
      </Button>
 | 
			
		||||
      <Confirm
 | 
			
		||||
        isOpen={open}
 | 
			
		||||
        loading={isLoading}
 | 
			
		||||
        onConfirm={handleConfirm}
 | 
			
		||||
        onClose={handleDialogClose}
 | 
			
		||||
        title="resources.devices.action.erase.title"
 | 
			
		||||
        content="resources.devices.action.erase.content"
 | 
			
		||||
        translateOptions={{
 | 
			
		||||
          id: record.id,
 | 
			
		||||
          name: record.display_name ? record.display_name : record.id,
 | 
			
		||||
        }}
 | 
			
		||||
      />
 | 
			
		||||
    </>
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
							
								
								
									
										51
									
								
								src/components/devices.jsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										51
									
								
								src/components/devices.jsx
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,51 @@
 | 
			
		||||
import React from "react";
 | 
			
		||||
import {
 | 
			
		||||
  DeleteButton,
 | 
			
		||||
  useDelete,
 | 
			
		||||
  useNotify,
 | 
			
		||||
  useRecordContext,
 | 
			
		||||
  useRefresh,
 | 
			
		||||
} from "react-admin";
 | 
			
		||||
 | 
			
		||||
export const DeviceRemoveButton = props => {
 | 
			
		||||
  const record = useRecordContext();
 | 
			
		||||
  const refresh = useRefresh();
 | 
			
		||||
  const notify = useNotify();
 | 
			
		||||
 | 
			
		||||
  const [removeDevice] = useDelete();
 | 
			
		||||
 | 
			
		||||
  if (!record) return null;
 | 
			
		||||
 | 
			
		||||
  const handleConfirm = () => {
 | 
			
		||||
    removeDevice(
 | 
			
		||||
      "devices",
 | 
			
		||||
      // needs previousData for user_id
 | 
			
		||||
      { id: record.id, previousData: record },
 | 
			
		||||
      {
 | 
			
		||||
        onSuccess: () => {
 | 
			
		||||
          notify("resources.devices.action.erase.success");
 | 
			
		||||
          refresh();
 | 
			
		||||
        },
 | 
			
		||||
        onError: () => {
 | 
			
		||||
          notify("resources.devices.action.erase.failure", { type: "error" });
 | 
			
		||||
        },
 | 
			
		||||
      }
 | 
			
		||||
    );
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <DeleteButton
 | 
			
		||||
      {...props}
 | 
			
		||||
      label="ra.action.remove"
 | 
			
		||||
      confirmTitle="resources.devices.action.erase.title"
 | 
			
		||||
      confirmContent="resources.devices.action.erase.content"
 | 
			
		||||
      onConfirm={handleConfirm}
 | 
			
		||||
      mutationMode="pessimistic"
 | 
			
		||||
      redirect={false}
 | 
			
		||||
      translateOptions={{
 | 
			
		||||
        id: record.id,
 | 
			
		||||
        name: record.display_name ? record.display_name : record.id,
 | 
			
		||||
      }}
 | 
			
		||||
    />
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
@ -98,6 +98,7 @@ export const RoomShow = props => {
 | 
			
		||||
        <Tab label="synapseadmin.rooms.tabs.basic" icon={<ViewListIcon />}>
 | 
			
		||||
          <TextField source="room_id" />
 | 
			
		||||
          <TextField source="name" />
 | 
			
		||||
          <TextField source="topic" />
 | 
			
		||||
          <TextField source="canonical_alias" />
 | 
			
		||||
          <ReferenceField source="creator" reference="users">
 | 
			
		||||
            <TextField source="id" />
 | 
			
		||||
@ -189,7 +189,7 @@ const de = {
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
    reports: {
 | 
			
		||||
      name: "Ereignisbericht |||| Ereignisberichte",
 | 
			
		||||
      name: "Gemeldetes Ereignis |||| Gemeldete Ereignisse",
 | 
			
		||||
      fields: {
 | 
			
		||||
        id: "ID",
 | 
			
		||||
        received_ts: "Meldezeit",
 | 
			
		||||
@ -211,6 +211,13 @@ const de = {
 | 
			
		||||
          },
 | 
			
		||||
        },
 | 
			
		||||
      },
 | 
			
		||||
      action: {
 | 
			
		||||
        erase: {
 | 
			
		||||
          title: "Gemeldetes Event löschen",
 | 
			
		||||
          content:
 | 
			
		||||
            "Sind Sie sicher dass Sie das gemeldete Event löschen möchten? Diese Aktion kann nicht rückgängig gemacht werden.",
 | 
			
		||||
        },
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
    connections: {
 | 
			
		||||
      name: "Verbindungen",
 | 
			
		||||
 | 
			
		||||
@ -208,6 +208,13 @@ const en = {
 | 
			
		||||
          },
 | 
			
		||||
        },
 | 
			
		||||
      },
 | 
			
		||||
      action: {
 | 
			
		||||
        erase: {
 | 
			
		||||
          title: "Delete reported event",
 | 
			
		||||
          content:
 | 
			
		||||
            "Are you sure you want to delete the reported event? This cannot be undone.",
 | 
			
		||||
        },
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
    connections: {
 | 
			
		||||
      name: "Connections",
 | 
			
		||||
 | 
			
		||||
@ -1,5 +0,0 @@
 | 
			
		||||
import React from "react";
 | 
			
		||||
import ReactDOM from "react-dom";
 | 
			
		||||
import App from "./App";
 | 
			
		||||
 | 
			
		||||
ReactDOM.render(<App />, document.getElementById("root"));
 | 
			
		||||
							
								
								
									
										9
									
								
								src/index.jsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								src/index.jsx
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,9 @@
 | 
			
		||||
import React from "react";
 | 
			
		||||
import { createRoot } from "react-dom/client";
 | 
			
		||||
import App from "./App";
 | 
			
		||||
 | 
			
		||||
createRoot(document.getElementById("root")).render(
 | 
			
		||||
  <React.StrictMode>
 | 
			
		||||
    <App />
 | 
			
		||||
  </React.StrictMode>
 | 
			
		||||
);
 | 
			
		||||
@ -456,7 +456,7 @@ const dataProvider = {
 | 
			
		||||
    const res = resourceMap[resource];
 | 
			
		||||
 | 
			
		||||
    const endpoint_url = homeserver + res.path;
 | 
			
		||||
    return jsonClient(`${endpoint_url}/${encodeURIComponent(params.data.id)}`, {
 | 
			
		||||
    return jsonClient(`${endpoint_url}/${encodeURIComponent(params.id)}`, {
 | 
			
		||||
      method: "PUT",
 | 
			
		||||
      body: JSON.stringify(params.data, filterNullValues),
 | 
			
		||||
    }).then(({ json }) => ({
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										48
									
								
								src/synapse/synapse.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										48
									
								
								src/synapse/synapse.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,48 @@
 | 
			
		||||
import { fetchUtils } from "react-admin";
 | 
			
		||||
 | 
			
		||||
export const splitMxid = mxid => {
 | 
			
		||||
  const re =
 | 
			
		||||
    /^@(?<name>[a-zA-Z0-9._=\-/]+):(?<domain>[a-zA-Z0-9\-.]+\.[a-zA-Z]+)$/;
 | 
			
		||||
  return re.exec(mxid)?.groups;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const isValidBaseUrl = baseUrl =>
 | 
			
		||||
  /^(http|https):\/\/[a-zA-Z0-9\-.]+(:\d{1,5})?$/.test(baseUrl);
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Resolve the homeserver URL using the well-known lookup
 | 
			
		||||
 * @param domain  the domain part of an MXID
 | 
			
		||||
 * @returns homeserver base URL
 | 
			
		||||
 */
 | 
			
		||||
export const getWellKnownUrl = async domain => {
 | 
			
		||||
  const wellKnownUrl = `https://${domain}/.well-known/matrix/client`;
 | 
			
		||||
  try {
 | 
			
		||||
    const json = await fetchUtils.fetchJson(wellKnownUrl, { method: "GET" });
 | 
			
		||||
    return json["m.homeserver"].base_url;
 | 
			
		||||
  } catch {
 | 
			
		||||
    // if there is no .well-known entry, return the domain itself
 | 
			
		||||
    return `https://${domain}`;
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Get synapse server version
 | 
			
		||||
 * @param base_url  the base URL of the homeserver
 | 
			
		||||
 * @returns server version
 | 
			
		||||
 */
 | 
			
		||||
export const getServerVersion = async baseUrl => {
 | 
			
		||||
  const versionUrl = `${baseUrl}/_synapse/admin/v1/server_version`;
 | 
			
		||||
  const response = await fetchUtils.fetchJson(versionUrl, { method: "GET" });
 | 
			
		||||
  return response.json.server_version;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Get supported login flows
 | 
			
		||||
 * @param baseUrl  the base URL of the homeserver
 | 
			
		||||
 * @returns array of supported login flows
 | 
			
		||||
 */
 | 
			
		||||
export const getSupportedLoginFlows = async baseUrl => {
 | 
			
		||||
  const loginFlowsUrl = `${baseUrl}/_matrix/client/r0/login`;
 | 
			
		||||
  const response = await fetchUtils.fetchJson(loginFlowsUrl, { method: "GET" });
 | 
			
		||||
  return response.json.flows;
 | 
			
		||||
};
 | 
			
		||||
							
								
								
									
										31
									
								
								src/synapse/synapse.test.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								src/synapse/synapse.test.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,31 @@
 | 
			
		||||
import { isValidBaseUrl, splitMxid } from "./synapse";
 | 
			
		||||
 | 
			
		||||
describe("splitMxid", () => {
 | 
			
		||||
  it("splits valid MXIDs", () =>
 | 
			
		||||
    expect(splitMxid("@name:domain.tld")).toEqual({
 | 
			
		||||
      name: "name",
 | 
			
		||||
      domain: "domain.tld",
 | 
			
		||||
    }));
 | 
			
		||||
  it("rejects invalid MXIDs", () => expect(splitMxid("foo")).toBeUndefined());
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
describe("isValidBaseUrl", () => {
 | 
			
		||||
  it("accepts a http URL", () =>
 | 
			
		||||
    expect(isValidBaseUrl("http://foo.bar")).toBeTruthy());
 | 
			
		||||
  it("accepts a https URL", () =>
 | 
			
		||||
    expect(isValidBaseUrl("https://foo.bar")).toBeTruthy());
 | 
			
		||||
  it("accepts a valid URL with port", () =>
 | 
			
		||||
    expect(isValidBaseUrl("https://foo.bar:1234")).toBeTruthy());
 | 
			
		||||
  it("rejects undefined base URLs", () =>
 | 
			
		||||
    expect(isValidBaseUrl(undefined)).toBeFalsy());
 | 
			
		||||
  it("rejects null base URLs", () => expect(isValidBaseUrl(null)).toBeFalsy());
 | 
			
		||||
  it("rejects empty base URLs", () => expect(isValidBaseUrl("")).toBeFalsy());
 | 
			
		||||
  it("rejects non-string base URLs", () =>
 | 
			
		||||
    expect(isValidBaseUrl({})).toBeFalsy());
 | 
			
		||||
  it("rejects base URLs without protocol", () =>
 | 
			
		||||
    expect(isValidBaseUrl("foo.bar")).toBeFalsy());
 | 
			
		||||
  it("rejects base URLs with path", () =>
 | 
			
		||||
    expect(isValidBaseUrl("http://foo.bar/path")).toBeFalsy());
 | 
			
		||||
  it("rejects invalid base URLs", () =>
 | 
			
		||||
    expect(isValidBaseUrl("http:/foo.bar")).toBeFalsy());
 | 
			
		||||
});
 | 
			
		||||
		Loading…
	
		Reference in New Issue
	
	Block a user