diff --git a/app/routes/admin_pages.py b/app/routes/admin_pages.py
index 01625a15..d3486bb8 100644
--- a/app/routes/admin_pages.py
+++ b/app/routes/admin_pages.py
@@ -1274,6 +1274,69 @@ async def admin_platform_modules(
)
+@router.get(
+ "/platforms/{platform_code}/modules/{module_code}",
+ response_class=HTMLResponse,
+ include_in_schema=False,
+)
+async def admin_module_info(
+ request: Request,
+ platform_code: str = Path(..., description="Platform code"),
+ module_code: str = Path(..., description="Module code"),
+ current_user: User = Depends(require_menu_access("platforms", FrontendType.ADMIN)),
+ db: Session = Depends(get_db),
+):
+ """
+ Render module info/detail page.
+ Shows module details including features, menu items, dependencies,
+ and self-contained module information.
+ """
+ # Only super admins can access module details
+ if not current_user.is_super_admin:
+ return RedirectResponse(url=f"/admin/platforms/{platform_code}", status_code=302)
+
+ return templates.TemplateResponse(
+ "admin/module-info.html",
+ {
+ "request": request,
+ "user": current_user,
+ "platform_code": platform_code,
+ "module_code": module_code,
+ },
+ )
+
+
+@router.get(
+ "/platforms/{platform_code}/modules/{module_code}/config",
+ response_class=HTMLResponse,
+ include_in_schema=False,
+)
+async def admin_module_config(
+ request: Request,
+ platform_code: str = Path(..., description="Platform code"),
+ module_code: str = Path(..., description="Module code"),
+ current_user: User = Depends(require_menu_access("platforms", FrontendType.ADMIN)),
+ db: Session = Depends(get_db),
+):
+ """
+ Render module configuration page.
+ Allows configuring module-specific settings.
+ """
+ # Only super admins can access module configuration
+ if not current_user.is_super_admin:
+ return RedirectResponse(url=f"/admin/platforms/{platform_code}", status_code=302)
+
+ return templates.TemplateResponse(
+ "admin/module-config.html",
+ {
+ "request": request,
+ "user": current_user,
+ "platform_code": platform_code,
+ "module_code": module_code,
+ },
+ )
+
+
# ============================================================================
# CONTENT MANAGEMENT SYSTEM (CMS) ROUTES
# ============================================================================
diff --git a/app/templates/admin/module-info.html b/app/templates/admin/module-info.html
new file mode 100644
index 00000000..0a2af979
--- /dev/null
+++ b/app/templates/admin/module-info.html
@@ -0,0 +1,274 @@
+{# app/templates/admin/module-info.html #}
+{% extends "admin/base.html" %}
+{% from 'shared/macros/alerts.html' import alert_dynamic, error_state, loading_state %}
+{% from 'shared/macros/headers.html' import page_header %}
+
+{% block title %}Module Details{% endblock %}
+
+{% block alpine_data %}adminModuleInfo('{{ platform_code }}', '{{ module_code }}'){% endblock %}
+
+{% block content %}
+
+
+
+
+
+
+
+ Configure
+
+
+
+
+ Enabled
+
+
+ Disabled
+
+
+ Core
+
+
+
+
+
+
+
+{{ alert_dynamic(type='success', title='Success', message_var='successMessage', show_condition='successMessage') }}
+{{ error_state('Error', show_condition='error') }}
+{{ loading_state('Loading module details...') }}
+
+
+
+
+
+
+
+
+
+
Features
+
+
+
+
+
+
No features defined for this module.
+
+
+
+
+
+
+
+
+ Admin Menu Items
+
+
+
No admin menu items.
+
+
+
+
+
+
+ Vendor Menu Items
+
+
+
No vendor menu items.
+
+
+
+
+
+
+
+
+
+ Dependencies
+ (requires)
+
+
+
No dependencies.
+
+
+
+
+
+
+ Dependents
+ (required by)
+
+
+
No modules depend on this one.
+
+
+
+
+
+
+
+ Self-Contained Module
+
+
+
+
+
+
+
+
Services
+
+
Not defined
+
+
+
+
+
+
+
+
+
Models
+
+
Not defined
+
+
+
+
+
+
+
+
+
Templates
+
+
Not defined
+
+
+
+
+
+
+
+
+
Locales
+
+
Not defined
+
+
+
+
+
+
+
+
+
Static Files
+
+
Not defined
+
+
+
+
+
+
+
+
+
API Routes
+
+
Not defined
+
+
+
+
+
+
+
+
Module Information
+
+
+ Module Type
+ Core
+ Optional
+
+
+ Self-Contained
+ Yes
+ No
+
+
+ Has Configuration
+ Yes
+ No
+
+
+
+
+
+
+{% endblock %}
+
+{% block extra_scripts %}
+
+{% endblock %}
diff --git a/app/templates/admin/platform-modules.html b/app/templates/admin/platform-modules.html
index bc7968fb..cd52707d 100644
--- a/app/templates/admin/platform-modules.html
+++ b/app/templates/admin/platform-modules.html
@@ -136,6 +136,21 @@
+
+
+
+
+
+
+
+
+
+
Enabled
@@ -202,6 +217,21 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/static/admin/js/module-info.js b/static/admin/js/module-info.js
new file mode 100644
index 00000000..6512321e
--- /dev/null
+++ b/static/admin/js/module-info.js
@@ -0,0 +1,166 @@
+// static/admin/js/module-info.js
+// Module info/detail page management
+
+const moduleInfoLog = window.LogConfig?.loggers?.moduleInfo || window.LogConfig?.createLogger?.('moduleInfo') || console;
+
+function adminModuleInfo(platformCode, moduleCode) {
+ return {
+ // Inherit base layout functionality from init-alpine.js
+ ...data(),
+
+ // Page-specific state
+ currentPage: 'platforms',
+ platformCode: platformCode,
+ moduleCode: moduleCode,
+ loading: true,
+ error: null,
+ successMessage: null,
+ saving: false,
+
+ // Data
+ platform: null,
+ module: null,
+
+ // Module icons mapping (must match icons.js definitions)
+ getModuleIcon(code) {
+ const icons = {
+ 'core': 'home',
+ 'platform-admin': 'office-building',
+ 'billing': 'credit-card',
+ 'inventory': 'archive',
+ 'orders': 'shopping-cart',
+ 'marketplace': 'shopping-bag',
+ 'customers': 'users',
+ 'cms': 'document-text',
+ 'analytics': 'chart-bar',
+ 'messaging': 'chat',
+ 'dev-tools': 'code',
+ 'monitoring': 'chart-pie'
+ };
+ return icons[code] || 'puzzle-piece';
+ },
+
+ // Modules with configuration options
+ hasConfig(code) {
+ return ['billing', 'inventory', 'orders', 'marketplace',
+ 'customers', 'cms', 'analytics', 'messaging', 'monitoring'].includes(code);
+ },
+
+ async init() {
+ // Guard against duplicate initialization
+ if (window._moduleInfoInitialized) {
+ moduleInfoLog.warn('Already initialized, skipping');
+ return;
+ }
+ window._moduleInfoInitialized = true;
+
+ moduleInfoLog.info('=== MODULE INFO PAGE INITIALIZING ===');
+ moduleInfoLog.info('Platform code:', this.platformCode);
+ moduleInfoLog.info('Module code:', this.moduleCode);
+
+ try {
+ await this.loadPlatform();
+ await this.loadModuleInfo();
+ moduleInfoLog.info('=== MODULE INFO PAGE INITIALIZED ===');
+ } catch (error) {
+ moduleInfoLog.error('Failed to initialize module info page:', error);
+ this.error = 'Failed to load page data. Please refresh.';
+ }
+ },
+
+ async refresh() {
+ this.error = null;
+ this.successMessage = null;
+ await this.loadModuleInfo();
+ },
+
+ async loadPlatform() {
+ try {
+ this.platform = await apiClient.get(`/admin/platforms/${this.platformCode}`);
+ moduleInfoLog.info('Loaded platform:', this.platform?.name);
+ } catch (error) {
+ moduleInfoLog.error('Failed to load platform:', error);
+ throw error;
+ }
+ },
+
+ async loadModuleInfo() {
+ this.loading = true;
+ this.error = null;
+
+ try {
+ const platformId = this.platform?.id;
+ if (!platformId) {
+ throw new Error('Platform not loaded');
+ }
+
+ // Get all modules and find the specific one
+ const moduleConfig = await apiClient.get(`/admin/modules/platforms/${platformId}`);
+ this.module = moduleConfig.modules?.find(m => m.code === this.moduleCode);
+
+ if (!this.module) {
+ throw new Error(`Module '${this.moduleCode}' not found`);
+ }
+
+ moduleInfoLog.info('Loaded module info:', {
+ code: this.module.code,
+ name: this.module.name,
+ is_enabled: this.module.is_enabled,
+ is_core: this.module.is_core
+ });
+ } catch (error) {
+ moduleInfoLog.error('Failed to load module info:', error);
+ this.error = error.message || 'Failed to load module information';
+ } finally {
+ this.loading = false;
+ }
+ },
+
+ async toggleModule() {
+ if (this.module?.is_core) {
+ moduleInfoLog.warn('Cannot toggle core module:', this.moduleCode);
+ return;
+ }
+
+ this.saving = true;
+ this.error = null;
+ this.successMessage = null;
+
+ const action = this.module.is_enabled ? 'disable' : 'enable';
+
+ try {
+ const platformId = this.platform?.id;
+ const endpoint = `/admin/modules/platforms/${platformId}/${action}`;
+
+ const result = await apiClient.post(endpoint, {
+ module_code: this.moduleCode
+ });
+
+ moduleInfoLog.info(`${action}d module:`, this.moduleCode, result);
+
+ // Show success message
+ if (result.also_enabled?.length > 0) {
+ this.successMessage = `Module '${this.module.name}' enabled. Also enabled dependencies: ${result.also_enabled.join(', ')}`;
+ } else if (result.also_disabled?.length > 0) {
+ this.successMessage = `Module '${this.module.name}' disabled. Also disabled dependents: ${result.also_disabled.join(', ')}`;
+ } else {
+ this.successMessage = `Module '${this.module.name}' ${action}d successfully`;
+ }
+
+ // Reload module info to get updated state
+ await this.loadModuleInfo();
+
+ // Clear success message after delay
+ setTimeout(() => {
+ this.successMessage = null;
+ }, 5000);
+
+ } catch (error) {
+ moduleInfoLog.error(`Failed to ${action} module:`, error);
+ this.error = error.message || `Failed to ${action} module`;
+ } finally {
+ this.saving = false;
+ }
+ }
+ };
+}
diff --git a/static/admin/js/platform-modules.js b/static/admin/js/platform-modules.js
index 511e1d4c..999db5db 100644
--- a/static/admin/js/platform-modules.js
+++ b/static/admin/js/platform-modules.js
@@ -58,6 +58,12 @@ function adminPlatformModules(platformCode) {
return icons[moduleCode] || 'puzzle-piece';
},
+ // Modules with configuration options
+ hasConfig(moduleCode) {
+ return ['billing', 'inventory', 'orders', 'marketplace',
+ 'customers', 'cms', 'analytics', 'messaging', 'monitoring'].includes(moduleCode);
+ },
+
async init() {
// Guard against duplicate initialization
if (window._platformModulesInitialized) {