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"
>