Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,12 @@ Run this command to create files for distribution.

Result will be created in `/dist`

## Deploying under a subpath

By default the app assumes it is served at the root of its domain
(`https://mail.example.com/`). To run it under a subpath instead
(`https://mail.example.com/admin/`), only the `<base href="/">` tag
in `public/index.html` needs to be adapted.

# Server-side configuration

Expand Down
2 changes: 1 addition & 1 deletion conf/web.conf
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ root /usr/share/grommunio-admin-web;
location / {
#root /usr/share/grommunio-admin-web;
index index.html /index.html;
try_files $uri /index.html =404;
try_files $uri index.html =404;
}
#location /service-worker.js {
# add_header Cache-Control "no-cache, no-store, must-revalidate";
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
"name": "grommunio-admin-web",
"version": "3.0.0",
"private": true,
"homepage": ".",
"dependencies": {
"@emotion/react": "^11.10.5",
"@emotion/styled": "^11.10.5",
Expand Down
5 changes: 5 additions & 0 deletions public/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@
<html lang="en">
<head>
<meta charset="utf-8" />
<!--
Base URL every relative path in this app is resolved against.
Defaults to "/" for root deployments.
-->
<base href="/" />
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
Expand Down
2 changes: 1 addition & 1 deletion src/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { NewServer, ServerPolicy, UpdateServer } from './types/servers';
import { FetchSyncParams, RemoteWipeParams } from './types/sync';
import { AddUserFolderPermission, DeleteOrphanedUsersParams, DeleteUserFolderPermission, NewUser, OofSettings, UpdateUser, USER_STATUS } from './types/users';

const baseUrl = '//' + window.location.host + '/api/v1';
const baseUrl = 'api/v1';

/**
* Handles fetch api errors
Expand Down
7 changes: 4 additions & 3 deletions src/components/AuthenticatedDomainRoute.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { DomainViewProps } from "@/types/common";
import { Domain } from "@/types/domains";
import React from "react";

import { Navigate } from "react-router-dom";
import { Navigate, useLocation } from "react-router-dom";

type AuthenticatedDomainRouteProps = {
component: React.ComponentType<DomainViewProps>;
Expand All @@ -17,9 +17,10 @@ type AuthenticatedDomainRouteProps = {
}

const AuthenticatedDomainRoute = ({ component: DomainRoute, props: childProps, domain, ...rest }: AuthenticatedDomainRouteProps) => {
const location = useLocation();
if(!childProps.authenticated) {
return <Navigate to={`/login?redirect=${window.location.pathname}${window.location
.search}${window.location.hash}`}/>
return <Navigate to={`/login?redirect=${location.pathname}${location
.search}${location.hash}`}/>
}

return <DomainRoute domain={domain} {...rest} {...childProps} />
Expand Down
5 changes: 3 additions & 2 deletions src/components/DefaultRedirect.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,15 @@
// SPDX-FileCopyrightText: 2020-2026 grommunio GmbH
/* eslint-disable react/prop-types */
import React from "react";
import { Navigate } from "react-router-dom";
import { Navigate, useLocation } from "react-router-dom";

/**
* Default route, which the user is redirected to, if the url does not match any specified route
*/
const DefaultRedirect = () => {
const location = useLocation();
return <Navigate
to={`/login?redirect=${window.location.pathname}${window.location.hash}`}
to={`/login?redirect=${location.pathname}${location.hash}`}
/>
};

Expand Down
2 changes: 1 addition & 1 deletion src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ const error = () => {
}

// Fetch config.js on server and merge with default config
fetch('//' + window.location.host + '/config.json')
fetch('config.json')
.then(async response => {
if (response.ok) {
try {
Expand Down
6 changes: 3 additions & 3 deletions src/containers/ContactDetails.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { useTranslation } from 'react-i18next';
import User from '../components/user/User';
import Contact from '../components/user/Contact';
import { editUserData, fetchUserData } from '../actions/users';
import { useNavigate } from 'react-router';
import { useNavigate, useParams } from 'react-router';
import HideFromSelect from '../components/HideFromSelect';
import { useAppDispatch } from '../store';
import { UpdateUser, UserProperties } from '@/types/users';
Expand Down Expand Up @@ -59,14 +59,14 @@ const ContactDetails = ({ domain }: DomainViewProps) => {
unsaved: false,
});
const navigate = useNavigate();
const { userID: userIDParam } = useParams<{ userID: string }>();

const fetch = async (domainID: number, userID: number) => await dispatch(fetchUserData(domainID, userID));
const edit = async (domainID: number, user: UpdateUser) => await dispatch(editUserData(domainID, user));

useEffect(() => {
const inner = async () => {
const splits = window.location.pathname.split('/');
const user = await fetch(parseInt(splits[1]), parseInt(splits[3]))
const user = await fetch(domain.ID, parseInt(userIDParam as string))
.catch(msg => setState({ ...state, snackbar: msg || 'Unknown error' }));
if(!user) return;
user.syncPolicy = user.syncPolicy || {};
Expand Down
9 changes: 4 additions & 5 deletions src/containers/DBFile.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import { Add, Delete } from '@mui/icons-material';
import { SYSTEM_ADMIN_WRITE } from '../constants';
import { CapabilityContext } from '../CapabilityContext';
import ViewWrapper from '../components/ViewWrapper';
import { useNavigate } from 'react-router';
import { useNavigate, useParams } from 'react-router';
import { ChangeEvent, KeyValuePair } from '@/types/common';
import { useAppDispatch } from '../store';

Expand Down Expand Up @@ -61,6 +61,7 @@ const DBFile = () => {
});
const context = useContext(CapabilityContext);
const navigate = useNavigate();
const { serviceName, fileName } = useParams<{ serviceName: string; fileName: string }>();

const fetch = async (service: string, filename: string) =>
await dispatch(fetchServiceFile(service, filename));
Expand All @@ -69,8 +70,7 @@ const DBFile = () => {

useEffect(() => {
const inner = async () => {
const splits = window.location.pathname.split('/');
const file = await fetch(splits[2], splits[3])
const file = await fetch(serviceName as string, fileName as string)
.catch((message: string) => setState({ ...state, snackbar: message || 'Unknown error' }));

if(!file?.data) return;
Expand Down Expand Up @@ -106,8 +106,7 @@ const DBFile = () => {
}

const handleEdit = () => {
const splits = window.location.pathname.split('/');
edit(splits[2], splits[3], { data: formatData(state.data) })
edit(serviceName as string, fileName as string, { data: formatData(state.data) })
.then(resp => setState({ ...state, snackbar: 'Success! ' + (resp?.message || '')}))
.catch(message => setState({ ...state, snackbar: message || 'Unknown error' }));
}
Expand Down
6 changes: 3 additions & 3 deletions src/containers/FolderDetails.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import { CapabilityContext } from '../CapabilityContext';
import ViewWrapper from '../components/ViewWrapper';
import FolderPermissions from '../components/Dialogs/FolderPermissions';
import { Info } from '@mui/icons-material';
import { useNavigate } from 'react-router';
import { useNavigate, useParams } from 'react-router';
import { useAppDispatch } from '../store';
import { ChangeEvent, DomainViewProps } from '@/types/common';
import { Folder, UpdateFolder } from '@/types/folders';
Expand Down Expand Up @@ -72,6 +72,7 @@ const FolderDetails = ({ domain }: DomainViewProps) => {
});
const context = useContext(CapabilityContext);
const navigate = useNavigate();
const { folderID: folderIDParam } = useParams<{ folderID: string }>();

const fetch = async (domainID: number, folderID: string) => await dispatch(fetchFolderDetails(domainID, folderID));
const edit = async (domainID: number, folder: UpdateFolder) => await dispatch(editFolderData(domainID, folder));
Expand All @@ -80,8 +81,7 @@ const FolderDetails = ({ domain }: DomainViewProps) => {

useEffect(() => {
const inner = async () => {
const splits = window.location.pathname.split('/');
const folderId = splits[3];
const folderId = folderIDParam as string;
// If folder is IPM_SUBTREE
if(folderId === IPM_SUBTREE_ID) {
setState({ ...state, folder: IPM_SUBTREE_OBJECT, readonly: true, loading: false });
Expand Down
6 changes: 3 additions & 3 deletions src/containers/UserDetails.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ import { fetchServersData } from '../actions/servers';
import Oof from '../components/user/Oof';
import Tabs from '../components/user/Tabs';
import Altnames from '../components/user/Altnames';
import { useNavigate } from 'react-router';
import { useNavigate, useParams } from 'react-router';
import { throttle } from 'lodash';
import { ChangeEvent, DomainViewProps } from '@/types/common';
import { useAppDispatch } from '../store';
Expand Down Expand Up @@ -174,6 +174,7 @@ const UserDetails = ({ domain }: DomainViewProps) => {
const [forwardError, setForwardError] = useState(false);
const context = useContext(CapabilityContext);
const navigate = useNavigate();
const { userID: userIDParam } = useParams<{ userID: string }>();

const fetch = async (domainID: number, userID: number) => await dispatch(fetchUserData(domainID, userID));
const fetchRoles = async () => await dispatch(fetchRolesData({ sort: 'name,asc', level: 0, limit: 100000 }))
Expand All @@ -188,8 +189,7 @@ const UserDetails = ({ domain }: DomainViewProps) => {

useEffect(() => {
const inner = async () => {
const splits = window.location.pathname.split('/');
const user = await fetch(parseInt(splits[1]), parseInt(splits[3]))
const user = await fetch(domain.ID, parseInt(userIDParam as string))
.catch(msg => setState({ ...state, snackbar: msg || 'Unknown error' }));
if(!user) return;
const defaultPolicy = user.defaultPolicy || {};
Expand Down
8 changes: 7 additions & 1 deletion src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,18 @@ import makeLoadableComponent from './lazy';
// Async loading support.
const LoadableApp = makeLoadableComponent(() => import('./App'));

// Derive the router basename from the document's <base href>, so the app
// works correctly no matter which subpath it is deployed under.
// Falls back to "/" if no <base> tag is present.
const basePathname = new URL(document.baseURI).pathname;
const basename = basePathname.endsWith('/') ? basePathname.slice(0, -1) : basePathname;

const container = document.getElementById('root') || document.createElement("div");
const root = ReactDOM.createRoot(container);
root.render(<Provider store={store}>
<StyledEngineProvider injectFirst>
<ToggleColorMode>
<BrowserRouter>
<BrowserRouter basename={basename}>
<LoadableApp />
</BrowserRouter>
</ToggleColorMode>
Expand Down