Compare commits

...

3 Commits

Author SHA1 Message Date
Manuel Stahl
36f9ce6b07 Use vite.js instead of react-scripts
- react-scripts are not maintained anymore
- vite.js is well suited for single page applications

See https://darekkay.com/blog/create-react-app-to-vite/

Change-Id: Ib884748e373094a640b576894ff67b98c3584ec8
2024-02-07 17:03:28 +01:00
Manuel Stahl
83c9704633 Use store from react-admin instead of localStorage
By default this is the same, but it can be changed.

Change-Id: Id1b11872b5f7bc83c18f8d74b03ba6401c277da0
2024-02-07 17:03:28 +01:00
Manuel Stahl
8c1546cd5a WIP: Extract process.env
Change-Id: I9efb1079c0c88e6e0272c5fda734a367aa8f84a3
2024-02-07 17:03:28 +01:00
11 changed files with 445 additions and 5971 deletions

View File

@ -1,6 +1,7 @@
# Exclude a bunch of stuff which can make the build context a larger than it needs to be
tests/
build/
dist/
lib/
node_modules/
electron_app/

1
.gitignore vendored
View File

@ -10,6 +10,7 @@
# production
/build
/dist
# misc
.DS_Store

149
index.html Normal file
View File

@ -0,0 +1,149 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="shortcut icon" href="./favicon.ico" />
<meta
name="viewport"
content="minimum-scale=1, initial-scale=1, width=device-width, shrink-to-fit=no"
/>
<meta name="theme-color" content="#000000" />
<meta
name="description"
content="Synapse-Admin"
/>
<!--
manifest.json provides metadata used when your web app is installed on a
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
-->
<link rel="manifest" href="./manifest.json" />
<!--
Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build.
Only files inside the `public` folder can be referenced from the HTML.
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<title>Synapse-Admin</title>
<style>
body {
margin: 0;
padding: 0;
font-family: sans-serif;
}
.loader-container {
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
background-color: #fafafa;
}
/* CSS Spinner from https://projects.lukehaas.me/css-loaders/ */
.loader,
.loader:before,
.loader:after {
border-radius: 50%;
}
.loader {
color: #283593;
font-size: 11px;
text-indent: -99999em;
margin: 55px auto;
position: relative;
width: 10em;
height: 10em;
box-shadow: inset 0 0 0 1em;
-webkit-transform: translateZ(0);
-ms-transform: translateZ(0);
transform: translateZ(0);
}
.loader:before,
.loader:after {
position: absolute;
content: '';
}
.loader:before {
width: 5.2em;
height: 10.2em;
background: #fafafa;
border-radius: 10.2em 0 0 10.2em;
top: -0.1em;
left: -0.1em;
-webkit-transform-origin: 5.2em 5.1em;
transform-origin: 5.2em 5.1em;
-webkit-animation: load2 2s infinite ease 1.5s;
animation: load2 2s infinite ease 1.5s;
}
.loader:after {
width: 5.2em;
height: 10.2em;
background: #fafafa;
border-radius: 0 10.2em 10.2em 0;
top: -0.1em;
left: 5.1em;
-webkit-transform-origin: 0px 5.1em;
transform-origin: 0px 5.1em;
-webkit-animation: load2 2s infinite ease;
animation: load2 2s infinite ease;
}
@-webkit-keyframes load2 {
0% {
-webkit-transform: rotate(0deg);
transform: rotate(0deg);
}
100% {
-webkit-transform: rotate(360deg);
transform: rotate(360deg);
}
}
@keyframes load2 {
0% {
-webkit-transform: rotate(0deg);
transform: rotate(0deg);
}
100% {
-webkit-transform: rotate(360deg);
transform: rotate(360deg);
}
}
</style>
<link rel="preconnect" href="https://fonts.gstatic.com" />
<link
href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500;700&display=swap"
rel="stylesheet"
/>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root">
<div class="loader-container">
<div class="loader">Loading...</div>
</div>
</div>
<footer
style="position: relative; z-index: 2; height: 2em; margin-top: -2em; line-height: 2em; background-color: #eee; border: 0.5px solid #ddd">
<a id="copyright" href="https://github.com/Awesome-Technologies/synapse-admin"
style="margin-left: 1em; color: #888; font-family: Roboto, Helvetica, Arial, sans-serif; font-weight: 100; font-size: 0.8em; text-decoration: none;">
Synapse-Admin <b>(%REACT_APP_VERSION%)</b> by Awesome Technologies Innovationslabor GmbH
</a>
</footer>
</body>
<script type="module" src="/src/index.jsx"></script>
</html>

View File

@ -13,12 +13,14 @@
"@testing-library/jest-dom": "^6.0.0",
"@testing-library/react": "^14.0.0",
"@testing-library/user-event": "^14.5.2",
"@vitejs/plugin-react": "^4.0.0",
"eslint": "^8.56.0",
"eslint-config-prettier": "^9.1.0",
"eslint-config-react-app": "^7.0.1",
"eslint-plugin-prettier": "^5.1.3",
"jest-fetch-mock": "^3.0.3",
"prettier": "^3.2.5"
"prettier": "^3.2.5",
"vite": "^4.0.0"
},
"dependencies": {
"@mui/icons-material": "^5.15.7",
@ -31,12 +33,11 @@
"ra-language-italian": "^3.13.1",
"react": "^18.0.0",
"react-admin": "^4.16.9",
"react-dom": "^18.0.0",
"react-scripts": "^5.0.1"
"react-dom": "^18.0.0"
},
"scripts": {
"start": "REACT_APP_VERSION=$(git describe --tags) react-scripts start",
"build": "REACT_APP_VERSION=$(git describe --tags) react-scripts build",
"start": "REACT_APP_VERSION=$(git describe --tags) vite serve",
"build": "REACT_APP_VERSION=$(git describe --tags) vite build",
"fix:other": "yarn prettier --write",
"fix:code": "yarn test:lint --fix",
"fix": "yarn fix:code && yarn fix:other",
@ -44,8 +45,7 @@
"test:code": "react-scripts test",
"test:lint": "eslint --ignore-path .gitignore --ext .js,.jsx .",
"test:style": "yarn prettier --list-different",
"test": "yarn test:style && yarn test:lint && yarn test:code",
"eject": "react-scripts eject"
"test": "yarn test:style && yarn test:lint && yarn test:code"
},
"eslintConfig": {
"extends": "react-app"

View File

@ -24,6 +24,8 @@ import frenchMessages from "./i18n/fr";
import chineseMessages from "./i18n/zh";
import italianMessages from "./i18n/it";
const fixed_base_url = undefined; // FIXME: process.env.REACT_APP_SERVER;
// TODO: Can we use lazy loading together with browser locale?
const messages = {
de: germanMessages,
@ -42,7 +44,7 @@ const App = () => (
disableTelemetry
requireAuth
loginPage={LoginPage}
authProvider={authProvider}
authProvider={authProvider(fixed_base_url)}
dataProvider={dataProvider}
i18nProvider={i18nProvider}
>

View File

@ -7,6 +7,7 @@ import {
useLogin,
useNotify,
useLocaleState,
useStoreContext,
useTranslate,
PasswordInput,
TextInput,
@ -81,15 +82,15 @@ const FormBox = styled(Box)(({ theme }) => ({
},
}));
const LoginPage = () => {
const LoginPage = ({ cfg_base_url }) => {
const login = useLogin();
const notify = useNotify();
const store = useStoreContext();
const [loading, setLoading] = useState(false);
const [supportPassAuth, setSupportPassAuth] = useState(true);
const [locale, setLocale] = useLocaleState();
const translate = useTranslate();
const base_url = localStorage.getItem("base_url");
const cfg_base_url = process.env.REACT_APP_SERVER;
const base_url = store.getItem("base_url");
const [ssoBaseUrl, setSSOBaseUrl] = useState("");
const loginToken = /\?loginToken=([a-zA-Z0-9_-]+)/.exec(window.location.href);
@ -102,8 +103,8 @@ const LoginPage = () => {
"",
window.location.href.replace(loginToken[0], "#").split("#")[0]
);
const baseUrl = localStorage.getItem("sso_base_url");
localStorage.removeItem("sso_base_url");
const baseUrl = store.getItem("sso_base_url");
store.removeItem("sso_base_url");
if (baseUrl) {
const auth = {
base_url: baseUrl,
@ -169,7 +170,7 @@ const LoginPage = () => {
};
const handleSSO = () => {
localStorage.setItem("sso_base_url", ssoBaseUrl);
store.setItem("sso_base_url", ssoBaseUrl);
const ssoFullUrl = `${ssoBaseUrl}/_matrix/client/r0/login/sso/redirect?redirectUrl=${encodeURIComponent(
window.location.href
)}`;
@ -247,7 +248,7 @@ const LoginPage = () => {
name="base_url"
component={renderInput}
label="synapseadmin.auth.base_url"
disabled={cfg_base_url || loading}
disabled={cfg_base_url != null || loading}
resettable
fullWidth
className="input"

View File

@ -1,8 +1,10 @@
import React from "react";
import { createRoot } from "react-dom/client";
import ReactDOM from "react-dom/client";
import App from "./App";
createRoot(document.getElementById("root")).render(
const REACT_APP_SERVER = import.meta.env.VITE_APP_SERVER;
ReactDOM.createRoot(document.getElementById("root")).render(
<React.StrictMode>
<App />
</React.StrictMode>

View File

@ -1,10 +1,10 @@
import { fetchUtils } from "react-admin";
const authProvider = {
const authProvider = (fixed_base_url, store) => ({
// called when the user attempts to log in
login: ({ base_url, username, password, loginToken }) => {
// force homeserver for protection in case the form is manipulated
base_url = process.env.REACT_APP_SERVER || base_url;
base_url = fixed_base_url || base_url;
console.log("login ");
const options = {
@ -12,7 +12,7 @@ const authProvider = {
body: JSON.stringify(
Object.assign(
{
device_id: localStorage.getItem("device_id"),
device_id: store.getItem("device_id"),
initial_device_display_name: "Synapse Admin",
},
loginToken
@ -33,16 +33,16 @@ const authProvider = {
// server, since the admin might want to access the admin API via some
// private address
base_url = base_url.replace(/\/+$/g, "");
localStorage.setItem("base_url", base_url);
store.setItem("base_url", base_url);
const decoded_base_url = window.decodeURIComponent(base_url);
const login_api_url = decoded_base_url + "/_matrix/client/r0/login";
return fetchUtils.fetchJson(login_api_url, options).then(({ json }) => {
localStorage.setItem("home_server", json.home_server);
localStorage.setItem("user_id", json.user_id);
localStorage.setItem("access_token", json.access_token);
localStorage.setItem("device_id", json.device_id);
store.setItem("home_server", json.home_server);
store.setItem("user_id", json.user_id);
store.setItem("access_token", json.access_token);
store.setItem("device_id", json.device_id);
});
},
// called when the user clicks on the logout button
@ -86,6 +86,6 @@ const authProvider = {
},
// called when the user navigates to a new location, to check for permissions / roles
getPermissions: () => Promise.resolve(),
};
});
export default authProvider;

6
vite.config.js Normal file
View File

@ -0,0 +1,6 @@
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
export default defineConfig({
plugins: [react()],
});

6200
yarn.lock

File diff suppressed because it is too large Load Diff