diff --git a/AdminPanel/client/.env b/AdminPanel/client/.env index 56dee1ac..d6721def 100644 --- a/AdminPanel/client/.env +++ b/AdminPanel/client/.env @@ -1 +1,2 @@ -REACT_APP_BACKEND_URL=http://localhost:9000 \ No newline at end of file +REACT_APP_BACKEND_URL=http://localhost:9000 +REACT_APP_BASE_PATH=/admin diff --git a/AdminPanel/client/.env.example b/AdminPanel/client/.env.example index 56dee1ac..b6f8791f 100644 --- a/AdminPanel/client/.env.example +++ b/AdminPanel/client/.env.example @@ -1 +1,12 @@ -REACT_APP_BACKEND_URL=http://localhost:9000 \ No newline at end of file +# Admin Panel Environment Variables +# Copy this file to .env for local development + +# Backend URL for API calls +REACT_APP_BACKEND_URL=http://localhost:9000 + +# Base path for the admin panel (empty for root deployment) +# Examples: +# REACT_APP_BASE_PATH= # Root deployment: http://localhost:3000/ +# REACT_APP_BASE_PATH=/admin # Under /admin: http://localhost:3000/admin/ +# REACT_APP_BASE_PATH=/docserver-admin # Under /docserver-admin: http://localhost:3000/docserver-admin/ +REACT_APP_BASE_PATH=/admin diff --git a/AdminPanel/client/package-lock.json b/AdminPanel/client/package-lock.json index 8eed51d7..1ba9d979 100644 --- a/AdminPanel/client/package-lock.json +++ b/AdminPanel/client/package-lock.json @@ -2768,6 +2768,12 @@ "is-obj": "^2.0.0" } }, + "dotenv": { + "version": "17.2.2", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.2.2.tgz", + "integrity": "sha512-Sf2LSQP+bOlhKWWyhFsn0UsfdK/kCWRv1iuA2gXAwt3dyNabr6QSj00I2V10pidqz69soatm9ZwZvpQMTIOd5Q==", + "dev": true + }, "dunder-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", diff --git a/AdminPanel/client/package.json b/AdminPanel/client/package.json index 6295dc9d..7884270b 100644 --- a/AdminPanel/client/package.json +++ b/AdminPanel/client/package.json @@ -3,7 +3,7 @@ "version": "1.3.0", "private": true, "scripts": { - "start": "set \"REACT_APP_BACKEND_URL=http://localhost:9000\" && webpack serve --mode=development", + "start": "webpack serve --mode=development", "build": "webpack --mode=production" }, "dependencies": { @@ -29,6 +29,7 @@ "babel-loader": "8.2.0", "copy-webpack-plugin": "11.0.0", "css-loader": "^6.2.0", + "dotenv": "^17.2.2", "file-loader": "^6.2.0", "html-webpack-plugin": "5.5.0", "sass": "^1.77.0", diff --git a/AdminPanel/client/public/index.html b/AdminPanel/client/public/index.html index 81bb060f..9ae0a010 100644 --- a/AdminPanel/client/public/index.html +++ b/AdminPanel/client/public/index.html @@ -2,7 +2,7 @@ - + diff --git a/AdminPanel/client/src/App.js b/AdminPanel/client/src/App.js index cc16be57..39f57bab 100644 --- a/AdminPanel/client/src/App.js +++ b/AdminPanel/client/src/App.js @@ -1,32 +1,37 @@ import {Provider} from 'react-redux'; -import {Routes, Route, Navigate} from 'react-router-dom'; +import {Routes, Route, Navigate, BrowserRouter} from 'react-router-dom'; import './App.css'; import {store} from './store'; import AuthWrapper from './components/AuthWrapper/AuthWrapper'; import ConfigLoader from './components/ConfigLoader/ConfigLoader'; import Menu from './components/Menu/Menu'; import {menuItems} from './config/menuItems'; +import {getBasePath} from './utils/basePath'; function App() { + const basePath = getBasePath(); + return ( -
- -
- -
- - - } /> - {menuItems.map(item => ( - } /> - ))} - - + +
+ +
+ +
+ + + } /> + {menuItems.map(item => ( + } /> + ))} + + +
-
- -
+ +
+ ); } diff --git a/AdminPanel/client/src/api/index.js b/AdminPanel/client/src/api/index.js index 11e1a3a4..35b7dd02 100644 --- a/AdminPanel/client/src/api/index.js +++ b/AdminPanel/client/src/api/index.js @@ -1,7 +1,8 @@ const BACKEND_URL = process.env.REACT_APP_BACKEND_URL ?? ''; +const API_BASE_PATH = '/api/v1/admin'; export const fetchStatistics = async () => { - const response = await fetch(`${BACKEND_URL}/api/v1/admin/stat`); + const response = await fetch(`${BACKEND_URL}${API_BASE_PATH}/stat`); if (!response.ok) { throw new Error('Failed to fetch statistics'); } @@ -9,7 +10,7 @@ export const fetchStatistics = async () => { }; export const fetchConfiguration = async () => { - const response = await fetch(`${BACKEND_URL}/api/v1/admin/config`, { + const response = await fetch(`${BACKEND_URL}${API_BASE_PATH}/config`, { credentials: 'include' }); if (!response.ok) { @@ -19,7 +20,7 @@ export const fetchConfiguration = async () => { }; export const fetchConfigurationSchema = async () => { - const response = await fetch(`${BACKEND_URL}/api/v1/admin/config/schema`, { + const response = await fetch(`${BACKEND_URL}${API_BASE_PATH}/config/schema`, { credentials: 'include' }); if (!response.ok) { @@ -29,7 +30,7 @@ export const fetchConfigurationSchema = async () => { }; export const updateConfiguration = async configData => { - const response = await fetch(`${BACKEND_URL}/api/v1/admin/config`, { + const response = await fetch(`${BACKEND_URL}${API_BASE_PATH}/config`, { method: 'PATCH', headers: { 'Content-Type': 'application/json' @@ -54,7 +55,7 @@ export const updateConfiguration = async configData => { }; export const fetchCurrentUser = async () => { - const response = await fetch(`${BACKEND_URL}/api/v1/admin/me`, { + const response = await fetch(`${BACKEND_URL}${API_BASE_PATH}/me`, { method: 'GET', credentials: 'include' // Include cookies in the request }); @@ -70,7 +71,7 @@ export const fetchCurrentUser = async () => { }; export const login = async ({tenantName, secret}) => { - const response = await fetch(`${BACKEND_URL}/api/v1/admin/login`, { + const response = await fetch(`${BACKEND_URL}${API_BASE_PATH}/login`, { method: 'POST', headers: { 'Content-Type': 'application/json' @@ -90,7 +91,7 @@ export const login = async ({tenantName, secret}) => { }; export const logout = async () => { - const response = await fetch(`${BACKEND_URL}/api/v1/admin/logout`, { + const response = await fetch(`${BACKEND_URL}${API_BASE_PATH}/logout`, { method: 'POST', headers: { 'Content-Type': 'application/json' @@ -106,7 +107,7 @@ export const logout = async () => { }; export const rotateWopiKeys = async () => { - const response = await fetch(`${BACKEND_URL}/api/v1/admin/wopi/rotate-keys`, { + const response = await fetch(`${BACKEND_URL}${API_BASE_PATH}/wopi/rotate-keys`, { method: 'POST', headers: { 'Content-Type': 'application/json', diff --git a/AdminPanel/client/src/index.js b/AdminPanel/client/src/index.js index 6f50428e..5fbe9e45 100644 --- a/AdminPanel/client/src/index.js +++ b/AdminPanel/client/src/index.js @@ -1,6 +1,5 @@ import {StrictMode} from 'react'; import ReactDOM from 'react-dom/client'; -import {BrowserRouter} from 'react-router-dom'; import {QueryClient, QueryClientProvider} from '@tanstack/react-query'; import App from './App'; @@ -18,9 +17,7 @@ const root = ReactDOM.createRoot(document.getElementById('root')); root.render( - - - + ); diff --git a/AdminPanel/client/src/utils/basePath.js b/AdminPanel/client/src/utils/basePath.js new file mode 100644 index 00000000..92ac9204 --- /dev/null +++ b/AdminPanel/client/src/utils/basePath.js @@ -0,0 +1,15 @@ +/** + * Utility functions for handling BASE_PATH environment variable + */ + +// Get the base path from environment variable, with fallback to empty string +export const getBasePath = () => { + return process.env.REACT_APP_BASE_PATH || ''; +}; + +// Create a full path by combining base path with the given path +export const createPath = (path) => { + const basePath = getBasePath(); + const cleanPath = path.startsWith('/') ? path : `/${path}`; + return `${basePath}${cleanPath}`; +}; diff --git a/AdminPanel/client/webpack.config.js b/AdminPanel/client/webpack.config.js index 33fa3870..23c86507 100644 --- a/AdminPanel/client/webpack.config.js +++ b/AdminPanel/client/webpack.config.js @@ -2,6 +2,19 @@ const path = require('path'); const HtmlWebpackPlugin = require('html-webpack-plugin'); const CopyPlugin = require('copy-webpack-plugin'); const webpack = require('webpack'); +const dotenv = require('dotenv'); + +// Load environment variables from .env files +// Priority: .env.local > .env.development/.env.production > .env +const envFiles = [ + '.env.local', + process.env.NODE_ENV === 'production' ? '.env.production' : '.env.development', + '.env' +]; + +envFiles.forEach(file => { + dotenv.config({ path: file }); +}); module.exports = { entry: './src/index.js', @@ -39,7 +52,8 @@ module.exports = { ] }), new webpack.DefinePlugin({ - 'process.env.REACT_APP_BACKEND_URL': JSON.stringify(process.env.REACT_APP_BACKEND_URL) + 'process.env.REACT_APP_BACKEND_URL': JSON.stringify(process.env.REACT_APP_BACKEND_URL), + 'process.env.REACT_APP_BASE_PATH': JSON.stringify(process.env.REACT_APP_BASE_PATH || '') }) ],