diff --git a/src/components/superadmin/NewModuleModal.tsx b/src/components/superadmin/NewModuleModal.tsx index d62c3e2..78990a9 100644 --- a/src/components/superadmin/NewModuleModal.tsx +++ b/src/components/superadmin/NewModuleModal.tsx @@ -30,7 +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(), + 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') @@ -49,7 +49,7 @@ const newModuleSchema = z.object({ min_replicas: z.number().int().min(1, 'min_replicas must be at least 1').max(50, 'min_replicas must be at most 50').optional().nullable(), max_replicas: z.number().int().min(1, 'max_replicas must be at least 1').max(50, 'max_replicas must be at most 50').optional().nullable(), last_health_check: z.string().optional().nullable(), - health_status: z.string().max(20, 'health_status must be at most 20 characters').optional().nullable(), + // health_status: z.string().max(20, 'health_status must be at most 20 characters').optional().nullable(), consecutive_failures: z.number().int().optional().nullable(), registered_by: z.string().uuid().optional().nullable(), tenant_id: z.string().uuid().optional().nullable(), @@ -96,7 +96,7 @@ export const NewModuleModal = ({ min_replicas: null, max_replicas: null, last_health_check: null, - health_status: null, + // health_status: null, consecutive_failures: null, registered_by: null, tenant_id: null, @@ -126,7 +126,7 @@ export const NewModuleModal = ({ min_replicas: null, max_replicas: null, last_health_check: null, - health_status: null, + // health_status: null, consecutive_failures: null, registered_by: null, tenant_id: null, @@ -274,52 +274,52 @@ export const NewModuleModal = ({

{errors.root.message}

)} + {!apiKey && ( +
+ {/* Basic Information Section */} +
+

Basic Information

+
+
+
+ +
+
-
- {/* Basic Information Section */} -
-

Basic Information

-
-
-
- + +
-
- -
-
+ - - - {/*
+ {/*
*/} - - {/*
*/} - {/*
+ + {/*
*/} + {/*
*/} +
-
- {/* Runtime Information Section */} -
-

Runtime Information

-
-
+ {/* Runtime Information Section */} +
+

Runtime Information

+
+
+ +
+
+ +
+
+
+
+ +
+
+
+
+
+ + {/* URL Configuration Section */} +
+

URL Configuration

+
-
-
+
-
-
- -
-
-
-
-
- {/* URL Configuration Section */} -
-

URL Configuration

-
- + {/* Resource Configuration Section */} +
+

Resource Configuration (Optional)

+
+
+
+ +
+
+ +
+
- -
-
+
+
+ +
+
+ +
+
- {/* Resource Configuration Section */} -
-

Resource Configuration (Optional)

-
-
-
- -
-
- -
-
- -
-
- -
-
- -
-
- -
-
- { - if (value === '' || value === null || value === undefined) return null; - const num = Number(value); - return isNaN(num) ? null : num; - }, - })} - /> -
-
- { - if (value === '' || value === null || value === undefined) return null; - const num = Number(value); - return isNaN(num) ? null : num; - }, - })} - /> +
+
+ { + if (value === '' || value === null || value === undefined) return null; + const num = Number(value); + return isNaN(num) ? null : num; + }, + })} + /> +
+
+ { + if (value === '' || value === null || value === undefined) return null; + const num = Number(value); + return isNaN(num) ? null : num; + }, + })} + /> +
-
+ )} ); diff --git a/src/components/superadmin/RolesTable.tsx b/src/components/superadmin/RolesTable.tsx index 467bcba..419779b 100644 --- a/src/components/superadmin/RolesTable.tsx +++ b/src/components/superadmin/RolesTable.tsx @@ -288,7 +288,7 @@ export const RolesTable = ({ tenantId, showHeader = true, compact = false }: Rol <>
-

Roles

+

-

Users

+

{ {activeTab === 'roles' && id && ( )} - {activeTab === 'modules' && tenant && ( - + {activeTab === 'modules' && id && ( + )} {activeTab === 'settings' && tenant && ( @@ -386,31 +384,51 @@ const OverviewTab = ({ tenant, stats }: OverviewTabProps): ReactElement => { // Modules Tab Component interface ModulesTabProps { - modules: AssignedModule[]; + tenantId: string; } -const ModulesTab = ({ modules }: ModulesTabProps): ReactElement => { - const [enabledModules, setEnabledModules] = useState>(new Set()); +const ModulesTab = ({ tenantId }: ModulesTabProps): ReactElement => { + const [modules, setModules] = useState([]); + const [isLoading, setIsLoading] = useState(false); + const [error, setError] = useState(null); - useEffect(() => { - // Initialize enabled modules (assuming all are enabled by default) - setEnabledModules(new Set(modules.map((m) => m.id))); - }, [modules]); - - const toggleModule = (moduleId: string): void => { - setEnabledModules((prev) => { - const next = new Set(prev); - if (next.has(moduleId)) { - next.delete(moduleId); + // Fetch modules for this tenant + const fetchModules = async (): Promise => { + try { + setIsLoading(true); + setError(null); + const response = await moduleService.getMyModules(tenantId); + if (response.success && response.data) { + setModules(response.data); } else { - next.add(moduleId); + setError('Failed to load modules'); } - return next; - }); - // TODO: Call API to enable/disable module + } catch (err: any) { + setError(err?.response?.data?.error?.message || 'Failed to load modules'); + } finally { + setIsLoading(false); + } }; - const columns: Column[] = [ + useEffect(() => { + if (tenantId) { + fetchModules(); + } + }, [tenantId]); + + // Launch module handler + const handleLaunchModule = async (moduleId: string): Promise => { + try { + const response = await moduleService.launch(moduleId, tenantId); + if (response.success && response.data.launch_url) { + window.open(response.data.launch_url, '_blank', 'noopener,noreferrer'); + } + } catch (err: any) { + setError(err?.response?.data?.error?.message || 'Failed to launch module'); + } + }; + + const columns: Column[] = [ { key: 'name', label: 'Module Name', @@ -423,6 +441,13 @@ const ModulesTab = ({ modules }: ModulesTabProps): ReactElement => {
), }, + { + key: 'module_id', + label: 'Module ID', + render: (module) => ( + {module.module_id} + ), + }, { key: 'version', label: 'Version', @@ -448,45 +473,47 @@ const ModulesTab = ({ modules }: ModulesTabProps): ReactElement => { ), }, { - key: 'enabled', - label: 'Enabled', + key: 'base_url', + label: 'Base URL', render: (module) => ( - + + {module.base_url || 'N/A'} + ), }, { - key: 'created_at', - label: 'Registered', + key: 'assigned_at', + label: 'Assigned At', render: (module) => ( - {formatDate(module.created_at)} + {formatDate(module.assigned_at)} + ), + }, + { + key: 'actions', + label: 'Actions', + align: 'right', + render: (module) => ( +
+ +
), }, ]; return (
-
-

Modules

-
module.id} - isLoading={false} + isLoading={isLoading} + error={error} emptyMessage="No modules assigned to this tenant" />
@@ -593,9 +620,9 @@ const AuditLogsTab = ({ return (
-
+ {/*

Audit Logs

-
+
*/} (url); return response.data; }, - getMyModules: async (): Promise => { - const response = await apiClient.get('/modules/my'); + getMyModules: async (tenantId?: string | null): Promise => { + const params = new URLSearchParams(); + if (tenantId) { + params.append('tenant_id', tenantId); + } + const url = `/modules/my${params.toString() ? `?${params.toString()}` : ''}`; + const response = await apiClient.get(url); return response.data; }, };