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...') }} + + +
+ + +
+

Description

+

+
+ + +
+

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) {