diff --git a/docker-compose.yml b/docker-compose.yml index 7c33fa6..af48d97 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -15,7 +15,7 @@ services: networks: - space-network healthcheck: - test: [ "CMD", "mongo", "--username", "${MONGO_INITDB_ROOT_USERNAME:-root}", "--password", "${MONGO_INITDB_ROOT_PASSWORD:-4dm1n}", "--authenticationDatabase", "admin", "--eval", "db.adminCommand('ping')" ] + test: [ "CMD", "mongosh", "--username", "${MONGO_INITDB_ROOT_USERNAME:-root}", "--password", "${MONGO_INITDB_ROOT_PASSWORD:-4dm1n}", "--authenticationDatabase", "admin", "--eval", "db.adminCommand('ping')" ] interval: 5s timeout: 5s retries: 3 @@ -55,10 +55,10 @@ services: networks: - space-network healthcheck: - test: ["CMD", "curl", "-f", "http://localhost:3000/api/v1/healthcheck"] - interval: 1m30s - timeout: 30s - retries: 5 + test: ["CMD", "node", "-e", "require('http').get('http://localhost:3000/api/v1/healthcheck', r => process.exit(r.statusCode===200?0:1)).on('error', () => process.exit(1))"] + interval: 5s + timeout: 5s + retries: 3 start_period: 30s space-client: container_name: space-client @@ -68,10 +68,23 @@ services: args: VITE_ENVIRONMENT: production VITE_SPACE_BASE_URL: http://localhost:5403/api/v1 # Change to http://localhost/api/v1 if running SPACE in kubernetes + # VITE_FRONTEND_BASE_PATH: /space # Uncomment and set to the base path of the frontend if it's not served at the root (e.g., /space) depends_on: - space-server networks: - space-network + healthcheck: + test: + [ + "CMD", + "node", + "-e", + "fetch('http://localhost:5050').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))" + ] + interval: 10s + timeout: 5s + retries: 5 + start_period: 15s nginx: restart: always container_name: space-nginx @@ -85,6 +98,12 @@ services: - space-client networks: - space-network + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:5403/api/v1/healthcheck"] + interval: 5s + timeout: 5s + retries: 3 + start_period: 5s volumes: space-mongodb: driver: local diff --git a/frontend/docker/Dockerfile b/frontend/docker/Dockerfile index 564c0a2..dec6da9 100644 --- a/frontend/docker/Dockerfile +++ b/frontend/docker/Dockerfile @@ -17,6 +17,7 @@ RUN pnpm install # Copy the rest of the application source code to the container RUN mkdir src COPY ./src ./src +COPY ./public ./public # Copy tsconfig.json to the container COPY ./tsconfig.json . @@ -26,12 +27,30 @@ COPY ./vite.config.ts . COPY ./index.html . ARG VITE_ENVIRONMENT -ARG VITE_FRONTEND_BASE_PATH +ARG VITE_FRONTEND_BASE_PATH="/" ARG VITE_SPACE_BASE_URL +ENV VITE_FRONTEND_BASE_PATH=${VITE_FRONTEND_BASE_PATH} + + # Build your application RUN pnpm run build RUN pnpm add -D serve +# Conditional Reorganization Block: +# This only runs if VITE_FRONTEND_BASE_PATH is defined and is NOT equal to "/" +RUN if [ -n "$VITE_FRONTEND_BASE_PATH" ] && [ "$VITE_FRONTEND_BASE_PATH" != "/" ]; then \ + # Extract the directory name (e.g., from "/space/" to "space") to exclude it from the find command + FOLDER_NAME=$(echo "$VITE_FRONTEND_BASE_PATH" | sed 's/\///g'); \ + echo "Reorganizing dist for subpath: $VITE_FRONTEND_BASE_PATH"; \ + # Create the nested directory structure + mkdir -p ./dist${VITE_FRONTEND_BASE_PATH} && \ + # Move everything EXCEPT index.html and the newly created subfolder into the subfolder + find ./dist -mindepth 1 -maxdepth 1 ! -name "index.html" ! -name "$FOLDER_NAME" \ + -exec mv -t ./dist${VITE_FRONTEND_BASE_PATH}/ {} +; \ + else \ + echo "Skipping reorganization: App will be served from root (/)"; \ + fi + # Specify the command to start the server CMD ["pnpm", "exec", "serve", "-s", "./dist", "-l", "5050"] \ No newline at end of file diff --git a/frontend/index.html b/frontend/index.html index a31008a..ba4daaf 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -3,12 +3,12 @@ - - - - + + + + - + SPACE diff --git a/frontend/src/components/contracts/ContractsTable.tsx b/frontend/src/components/contracts/ContractsTable.tsx index 31001cc..5235c4e 100644 --- a/frontend/src/components/contracts/ContractsTable.tsx +++ b/frontend/src/components/contracts/ContractsTable.tsx @@ -188,7 +188,7 @@ export default function ContractsTable({ contracts, page, setPage, limit, setLim navigate(`${import.meta.env.VITE_FRONTEND_BASE_PATH}/contracts/${c.userContact?.userId}`)} + onClick={() => navigate(`${import.meta.env.BASE_URL}/contracts/${c.userContact?.userId}`)} className="px-3 py-1 bg-indigo-600 hover:bg-indigo-700 text-white rounded-md text-sm transition-colors cursor-pointer" > View diff --git a/frontend/src/layouts/logged-view/components/sidebar/index.tsx b/frontend/src/layouts/logged-view/components/sidebar/index.tsx index 2b492f9..25fe49b 100644 --- a/frontend/src/layouts/logged-view/components/sidebar/index.tsx +++ b/frontend/src/layouts/logged-view/components/sidebar/index.tsx @@ -7,34 +7,34 @@ import { AiOutlineDashboard } from 'react-icons/ai'; import OrganizationSelector from '@/components/OrganizationSelector'; const mainTabs = [ - { label: 'Overview', path: '/', icon: }, - { label: 'Contracts Dashboard', path: '/contracts/dashboard', icon: }, - { label: 'Members', path: '/members', icon: }, - { label: 'API Keys', path: '/api-keys', icon: }, - { label: 'Services Management', path: '/services', icon: }, + { label: 'Overview', path: '', icon: }, + { label: 'Contracts Dashboard', path: 'contracts/dashboard', icon: }, + { label: 'Members', path: 'members', icon: }, + { label: 'API Keys', path: 'api-keys', icon: }, + { label: 'Services Management', path: 'services', icon: }, ]; const settingsTabs = [ - { label: 'Organization Settings', path: '/organization-settings', icon: }, - { label: 'Profile Settings', path: '/settings', icon: }, + { label: 'Organization Settings', path: 'organization-settings', icon: }, + { label: 'Profile Settings', path: 'settings', icon: }, ]; const adminOnlyTabs = [ - { label: 'Users Management', path: '/users', icon: , adminOnly: true }, - { label: 'Organizations', path: '/organizations', icon: , adminOnly: true }, - { label: 'Instance Monitoring', path: '/instance-monitoring', icon: , adminOnly: true }, + { label: 'Users Management', path: 'users', icon: , adminOnly: true }, + { label: 'Organizations', path: 'organizations', icon: , adminOnly: true }, + { label: 'Instance Monitoring', path: 'instance-monitoring', icon: , adminOnly: true }, ]; function getSelectedTab(pathname: string) { - if (pathname.startsWith('/members')) return '/members'; - if (pathname.startsWith('/api-keys')) return '/api-keys'; - if (pathname.startsWith('/services')) return '/services'; - if (pathname.startsWith('/organization-settings')) return '/organization-settings'; - if (pathname.startsWith('/settings')) return '/settings'; - if (pathname.startsWith('/contracts/dashboard')) return '/contracts/dashboard'; - if (pathname.startsWith('/instance-monitoring')) return '/instance-monitoring'; - if (pathname.startsWith('/users')) return '/users'; - if (pathname.startsWith('/organizations')) return '/organizations'; + if (pathname.startsWith('members')) return 'members'; + if (pathname.startsWith('api-keys')) return 'api-keys'; + if (pathname.startsWith('services')) return 'services'; + if (pathname.startsWith('organization-settings')) return 'organization-settings'; + if (pathname.startsWith('settings')) return 'settings'; + if (pathname.startsWith('contracts/dashboard')) return 'contracts/dashboard'; + if (pathname.startsWith('instance-monitoring')) return 'instance-monitoring'; + if (pathname.startsWith('users')) return 'users'; + if (pathname.startsWith('organizations')) return 'organizations'; return '/'; } @@ -106,7 +106,7 @@ export default function Sidebar({ (selected === tab.path ? 'bg-indigo-100 dark:bg-gray-800 font-bold' : '') } - onClick={() => navigate(`${import.meta.env.VITE_FRONTEND_BASE_PATH}${tab.path}`)} + onClick={() => navigate(`${import.meta.env.BASE_URL}${tab.path}`)} aria-current={selected === tab.path ? 'page' : undefined} > {tab.icon} @@ -155,7 +155,7 @@ export default function Sidebar({ (selected === tab.path ? 'bg-indigo-100 dark:bg-gray-800 font-bold' : '') } - onClick={() => navigate(`${import.meta.env.VITE_FRONTEND_BASE_PATH}${tab.path}`)} + onClick={() => navigate(`${import.meta.env.BASE_URL}${tab.path}`)} aria-current={selected === tab.path ? 'page' : undefined} > {tab.icon} @@ -182,7 +182,7 @@ export default function Sidebar({ (selected === tab.path ? 'bg-indigo-100 dark:bg-gray-800 font-bold' : '') } - onClick={() => navigate(`${import.meta.env.VITE_FRONTEND_BASE_PATH}${tab.path}`)} + onClick={() => navigate(`${import.meta.env.BASE_URL}${tab.path}`)} aria-current={selected === tab.path ? 'page' : undefined} > {tab.icon} diff --git a/frontend/src/pages/contracts/ContractDetailPage.tsx b/frontend/src/pages/contracts/ContractDetailPage.tsx index 64903eb..5f54ba6 100644 --- a/frontend/src/pages/contracts/ContractDetailPage.tsx +++ b/frontend/src/pages/contracts/ContractDetailPage.tsx @@ -98,7 +98,7 @@ export default function ContractDetailPage() { navigate(`${import.meta.env.VITE_FRONTEND_BASE_PATH}/contracts/dashboard`)} + onClick={() => navigate(`${import.meta.env.BASE_URL}contracts/dashboard`)} className="inline-flex items-center px-4 py-2 bg-indigo-600 hover:bg-indigo-700 text-white font-medium rounded-lg transition-colors cursor-pointer" > @@ -125,7 +125,7 @@ export default function ContractDetailPage() { navigate(`${import.meta.env.VITE_FRONTEND_BASE_PATH}/contracts/dashboard`)} + onClick={() => navigate(`${import.meta.env.BASE_URL}contracts/dashboard`)} className="p-2 hover:bg-gray-100 dark:hover:bg-gray-800 rounded-lg transition-colors cursor-pointer" > @@ -166,7 +166,7 @@ export default function ContractDetailPage() { navigate(`${import.meta.env.VITE_FRONTEND_BASE_PATH}/contracts/dashboard`)} + onClick={() => navigate(`${import.meta.env.BASE_URL}contracts/dashboard`)} className="inline-flex items-center px-6 py-3 bg-indigo-600 hover:bg-indigo-700 text-white font-medium rounded-lg transition-colors shadow-lg hover:shadow-xl cursor-pointer" > diff --git a/frontend/src/pages/login/index.tsx b/frontend/src/pages/login/index.tsx index 5891e3d..bb05848 100644 --- a/frontend/src/pages/login/index.tsx +++ b/frontend/src/pages/login/index.tsx @@ -90,7 +90,7 @@ export default function LoginPage() {