diff --git a/src/components/layout/Sidebar.tsx b/src/components/layout/Sidebar.tsx index 95c85bf..30f1334 100644 --- a/src/components/layout/Sidebar.tsx +++ b/src/components/layout/Sidebar.tsx @@ -1,11 +1,11 @@ import { Link, useLocation } from 'react-router-dom'; -import { - LayoutDashboard, - Building2, - Users, - Package, - FileText, - Settings, +import { + LayoutDashboard, + Building2, + Users, + Package, + FileText, + Settings, HelpCircle, X, Shield @@ -77,7 +77,7 @@ export const Sidebar = ({ isOpen, onClose }: SidebarProps) => { }; const isSuperAdmin = isSuperAdminCheck(); - + // Fetch theme if tenant admin if (!isSuperAdmin) { useTenantTheme(); @@ -90,16 +90,16 @@ export const Sidebar = ({ isOpen, onClose }: SidebarProps) => { } const allowedActions = requiredAction ? [requiredAction] : ['*', 'read']; - + return permissions.some((perm) => { // Check if resource matches (exact match or wildcard) const resourceMatches = perm.resource === resource || perm.resource === '*'; - + // Check if action matches (exact match or wildcard) const actionMatches = allowedActions.some( (allowedAction) => perm.action === allowedAction || perm.action === '*' ); - + return resourceMatches && actionMatches; }); }; @@ -109,13 +109,13 @@ export const Sidebar = ({ isOpen, onClose }: SidebarProps) => { if (isSuperAdmin) { return items; // Show all items for super admin } - + return items.filter((item) => { // If no required permission, always show (e.g., Dashboard, Modules, Settings) if (!item.requiredPermission) { return true; } - + return hasPermission( item.requiredPermission.resource, item.requiredPermission.action @@ -134,8 +134,8 @@ export const Sidebar = ({ isOpen, onClose }: SidebarProps) => { const MenuSection = ({ title, items }: { title: string; items: MenuItem[] }) => (
-
-
+
+
{title}
@@ -154,7 +154,7 @@ export const Sidebar = ({ isOpen, onClose }: SidebarProps) => { } }} className={cn( - 'flex items-center gap-2.5 px-3 py-2 rounded-md transition-colors min-h-[44px]', + 'flex items-center gap-2 md:gap-2 lg:gap-2.5 px-2 md:px-2 lg:px-3 py-2 rounded-md transition-colors min-h-[44px]', isActive ? 'shadow-[0px_2px_8px_0px_rgba(15,23,42,0.15)]' : 'text-[#0f1724] hover:bg-gray-50' @@ -162,14 +162,14 @@ export const Sidebar = ({ isOpen, onClose }: SidebarProps) => { style={ isActive ? { - backgroundColor: !isSuperAdmin && theme?.primary_color ? theme.primary_color : '#112868', - color: !isSuperAdmin && theme?.secondary_color ? theme.secondary_color : '#23dce1', - } + backgroundColor: !isSuperAdmin && theme?.primary_color ? theme.primary_color : '#112868', + color: !isSuperAdmin && theme?.secondary_color ? theme.secondary_color : '#23dce1', + } : undefined } > - {item.label} + {item.label} ); })} @@ -192,9 +192,9 @@ export const Sidebar = ({ isOpen, onClose }: SidebarProps) => {
{!isSuperAdmin && logoUrl ? ( - Logo { e.currentTarget.style.display = 'none'; @@ -203,9 +203,9 @@ export const Sidebar = ({ isOpen, onClose }: SidebarProps) => { }} /> ) : null} -
{ {/* Desktop Sidebar */} - ); }; diff --git a/src/components/superadmin/NewModuleModal.tsx b/src/components/superadmin/NewModuleModal.tsx index adecdec..d62c3e2 100644 --- a/src/components/superadmin/NewModuleModal.tsx +++ b/src/components/superadmin/NewModuleModal.tsx @@ -30,6 +30,7 @@ const newModuleSchema = z.object({ .min(1, 'runtime_language is required') .max(50, 'runtime_language must be at most 50 characters'), framework: z.string().max(50, 'framework must be at most 50 characters').optional().nullable(), + webhookurl: z.string().max(500, "webhookurl must be at most 500 characters").url("Invalid URL format").nullable(), base_url: z .string() .min(1, 'base_url is required') @@ -85,6 +86,7 @@ export const NewModuleModal = ({ defaultValues: { description: null, framework: null, + webhookurl: null, endpoints: null, kafka_topics: null, cpu_request: null, @@ -112,6 +114,7 @@ export const NewModuleModal = ({ version: '', runtime_language: '', framework: null, + webhookurl: null, base_url: '', health_endpoint: '', endpoints: null, @@ -155,7 +158,7 @@ export const NewModuleModal = ({ if (error?.response?.data?.details && Array.isArray(error.response.data.details)) { const validationErrors = error.response.data.details; validationErrors.forEach((detail: { path: string; message: string }) => { - if (detail.path === 'name' || detail.path === 'module_id' || detail.path === 'description' || detail.path === 'version' || detail.path === 'runtime_language' || detail.path === 'framework' || detail.path === 'base_url' || detail.path === 'health_endpoint' || detail.path === 'endpoints' || detail.path === 'kafka_topics' || detail.path === 'cpu_request' || detail.path === 'cpu_limit' || detail.path === 'memory_request' || detail.path === 'memory_limit' || detail.path === 'min_replicas' || detail.path === 'max_replicas' || detail.path === 'last_health_check' || detail.path === 'health_status' || detail.path === 'consecutive_failures' || detail.path === 'registered_by' || detail.path === 'tenant_id' || detail.path === 'metadata') { + if (detail.path === 'name' || detail.path === 'module_id' || detail.path === 'description' || detail.path === 'version' || detail.path === 'runtime_language' || detail.path === 'framework' || detail.path === 'webhookurl' || detail.path === 'base_url' || detail.path === 'health_endpoint' || detail.path === 'endpoints' || detail.path === 'kafka_topics' || detail.path === 'cpu_request' || detail.path === 'cpu_limit' || detail.path === 'memory_request' || detail.path === 'memory_limit' || detail.path === 'min_replicas' || detail.path === 'max_replicas' || detail.path === 'last_health_check' || detail.path === 'health_status' || detail.path === 'consecutive_failures' || detail.path === 'registered_by' || detail.path === 'tenant_id' || detail.path === 'metadata') { setError(detail.path as keyof NewModuleFormData, { type: 'server', message: detail.message, @@ -352,6 +355,18 @@ export const NewModuleModal = ({ />
+
+
+ +
+
+
+
{/* URL Configuration Section */} diff --git a/src/components/superadmin/ViewModuleModal.tsx b/src/components/superadmin/ViewModuleModal.tsx index 1c6a034..420f980 100644 --- a/src/components/superadmin/ViewModuleModal.tsx +++ b/src/components/superadmin/ViewModuleModal.tsx @@ -152,6 +152,10 @@ export const ViewModuleModal = ({

{module.health_endpoint}

+
+ +

{module.webhookurl || '-'}

+
diff --git a/src/pages/superadmin/Modules.tsx b/src/pages/superadmin/Modules.tsx index 140a63f..d2a72a8 100644 --- a/src/pages/superadmin/Modules.tsx +++ b/src/pages/superadmin/Modules.tsx @@ -197,7 +197,7 @@ const Modules = (): ReactElement => { label: 'Actions', align: 'right', render: (module) => ( -
+
+
+ ), + }, ]; // Mobile card renderer @@ -131,6 +176,15 @@ const Modules = (): ReactElement => { {module.base_url || 'N/A'}

+
+ +
); diff --git a/src/services/module-service.ts b/src/services/module-service.ts index 714b273..fd267b4 100644 --- a/src/services/module-service.ts +++ b/src/services/module-service.ts @@ -1,5 +1,5 @@ import apiClient from './api-client'; -import type { ModulesResponse, GetModuleResponse, CreateModuleRequest, CreateModuleResponse } from '@/types/module'; +import type { ModulesResponse, GetModuleResponse, CreateModuleRequest, CreateModuleResponse, LaunchModuleResponse } from '@/types/module'; export const moduleService = { getAll: async ( @@ -66,4 +66,13 @@ export const moduleService = { const response = await apiClient.post('/modules', data); return response.data; }, + launch: async (id: string, tenantId?: string | null): Promise => { + const params = new URLSearchParams(); + if (tenantId) { + params.append('tenant_id', tenantId); + } + const url = `/modules/${id}/launch${params.toString() ? `?${params.toString()}` : ''}`; + const response = await apiClient.post(url); + return response.data; + }, }; diff --git a/src/types/module.ts b/src/types/module.ts index 6623ce1..5f574df 100644 --- a/src/types/module.ts +++ b/src/types/module.ts @@ -7,6 +7,7 @@ export interface Module { status: string; runtime_language: string | null; framework: string | null; + webhookurl: string | null; base_url: string; health_endpoint: string; endpoints: string[] | null; @@ -83,3 +84,20 @@ export interface CreateModuleResponse { }; message?: string; } + +export interface LaunchModuleResponse { + success: boolean; + data: { + launch_url: string; + expires_in: number; + module: { + id: string; + module_id: string; + name: string; + }; + tenant: { + id: string; + name: string; + }; + }; +} diff --git a/vite.config.ts b/vite.config.ts index 62192a8..ea8c085 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -11,46 +11,4 @@ export default defineConfig({ "@": path.resolve(__dirname, "src"), }, }, - // build: { - // rollupOptions: { - // output: { - // manualChunks(id) { - // // Feature-based chunks - only for source files - // if (!id.includes('node_modules')) { - // if (id.includes('/src/pages/superadmin/')) { - // return 'superadmin'; - // } - // if (id.includes('/src/pages/tenant/')) { - // return 'tenant'; - // } - // return; - // } - - // // Vendor chunks - group React ecosystem (including Redux) together - // // to avoid circular dependencies - // if ( - // id.includes('node_modules/react') || - // id.includes('node_modules/react-dom') || - // id.includes('node_modules/react-router') || - // id.includes('node_modules/@reduxjs') || - // id.includes('node_modules/redux') || - // id.includes('node_modules/react-redux') || - // id.includes('node_modules/scheduler') || - // id.includes('node_modules/object-assign') - // ) { - // return 'react-vendor'; - // } - - // // UI libraries - // if (id.includes('node_modules/lucide-react')) { - // return 'ui-vendor'; - // } - - // // All other node_modules go to vendor - // return 'vendor'; - // }, - // }, - // }, - // chunkSizeWarningLimit: 600, - // }, })