feat: add Admin UI for platform module management (Phase 5)
Added a complete Admin UI for managing platform modules:
API endpoints (app/api/v1/admin/modules.py):
- GET /modules - List all available modules
- GET /modules/platforms/{id} - Get platform modules
- PUT /modules/platforms/{id} - Update enabled modules
- POST /modules/platforms/{id}/enable - Enable a module
- POST /modules/platforms/{id}/disable - Disable a module
- POST /modules/platforms/{id}/enable-all - Enable all
- POST /modules/platforms/{id}/disable-optional - Core only
Admin UI:
- New page route: /admin/platforms/{code}/modules
- Template: platform-modules.html
- JavaScript: platform-modules.js
- Link added to platform-detail.html Super Admin section
Features:
- Toggle modules on/off with dependency resolution
- Enable all / Core only bulk actions
- Visual dependency indicators
- Separate sections for core vs optional modules
- Feature list preview per module
Also includes require_menu_access updates to page routes from Phase 2.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -61,6 +61,7 @@ from . import (
|
|||||||
media,
|
media,
|
||||||
menu_config,
|
menu_config,
|
||||||
messages,
|
messages,
|
||||||
|
modules,
|
||||||
monitoring,
|
monitoring,
|
||||||
notifications,
|
notifications,
|
||||||
order_item_exceptions,
|
order_item_exceptions,
|
||||||
@@ -125,6 +126,9 @@ router.include_router(platforms.router, tags=["admin-platforms"])
|
|||||||
# Include menu configuration endpoints (super admin only)
|
# Include menu configuration endpoints (super admin only)
|
||||||
router.include_router(menu_config.router, tags=["admin-menu-config"])
|
router.include_router(menu_config.router, tags=["admin-menu-config"])
|
||||||
|
|
||||||
|
# Include module management endpoints (super admin only)
|
||||||
|
router.include_router(modules.router, tags=["admin-modules"])
|
||||||
|
|
||||||
|
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
# User Management
|
# User Management
|
||||||
|
|||||||
399
app/api/v1/admin/modules.py
Normal file
399
app/api/v1/admin/modules.py
Normal file
@@ -0,0 +1,399 @@
|
|||||||
|
# app/api/v1/admin/modules.py
|
||||||
|
"""
|
||||||
|
Admin API endpoints for Platform Module Management.
|
||||||
|
|
||||||
|
Provides module enablement/disablement for platforms:
|
||||||
|
- GET /modules - List all available modules
|
||||||
|
- GET /modules/platforms/{platform_id} - Get modules for a platform
|
||||||
|
- PUT /modules/platforms/{platform_id} - Update enabled modules
|
||||||
|
- POST /modules/platforms/{platform_id}/enable - Enable a module
|
||||||
|
- POST /modules/platforms/{platform_id}/disable - Disable a module
|
||||||
|
|
||||||
|
All endpoints require super admin access.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from fastapi import APIRouter, Depends, Path
|
||||||
|
from pydantic import BaseModel, Field
|
||||||
|
from sqlalchemy.orm import Session
|
||||||
|
|
||||||
|
from app.api.deps import get_current_super_admin, get_db
|
||||||
|
from app.modules.registry import MODULES, get_core_module_codes
|
||||||
|
from app.modules.service import module_service
|
||||||
|
from app.services.platform_service import platform_service
|
||||||
|
from models.database.user import User
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
router = APIRouter(prefix="/modules")
|
||||||
|
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# Pydantic Schemas
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
|
||||||
|
class ModuleResponse(BaseModel):
|
||||||
|
"""Module definition response."""
|
||||||
|
|
||||||
|
code: str
|
||||||
|
name: str
|
||||||
|
description: str
|
||||||
|
is_core: bool
|
||||||
|
is_enabled: bool
|
||||||
|
requires: list[str] = Field(default_factory=list)
|
||||||
|
features: list[str] = Field(default_factory=list)
|
||||||
|
menu_items_admin: list[str] = Field(default_factory=list)
|
||||||
|
menu_items_vendor: list[str] = Field(default_factory=list)
|
||||||
|
dependent_modules: list[str] = Field(default_factory=list)
|
||||||
|
|
||||||
|
|
||||||
|
class ModuleListResponse(BaseModel):
|
||||||
|
"""Response for module list."""
|
||||||
|
|
||||||
|
modules: list[ModuleResponse]
|
||||||
|
total: int
|
||||||
|
enabled: int
|
||||||
|
disabled: int
|
||||||
|
|
||||||
|
|
||||||
|
class PlatformModulesResponse(BaseModel):
|
||||||
|
"""Response for platform module configuration."""
|
||||||
|
|
||||||
|
platform_id: int
|
||||||
|
platform_code: str
|
||||||
|
platform_name: str
|
||||||
|
modules: list[ModuleResponse]
|
||||||
|
total: int
|
||||||
|
enabled: int
|
||||||
|
disabled: int
|
||||||
|
|
||||||
|
|
||||||
|
class EnableModulesRequest(BaseModel):
|
||||||
|
"""Request to set enabled modules."""
|
||||||
|
|
||||||
|
module_codes: list[str] = Field(..., description="List of module codes to enable")
|
||||||
|
|
||||||
|
|
||||||
|
class ToggleModuleRequest(BaseModel):
|
||||||
|
"""Request to enable/disable a single module."""
|
||||||
|
|
||||||
|
module_code: str = Field(..., description="Module code to toggle")
|
||||||
|
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# Helper Functions
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
|
||||||
|
def _get_dependent_modules(module_code: str) -> list[str]:
|
||||||
|
"""Get modules that depend on a given module."""
|
||||||
|
dependents = []
|
||||||
|
for code, module in MODULES.items():
|
||||||
|
if module_code in module.requires:
|
||||||
|
dependents.append(code)
|
||||||
|
return dependents
|
||||||
|
|
||||||
|
|
||||||
|
def _build_module_response(
|
||||||
|
code: str,
|
||||||
|
is_enabled: bool,
|
||||||
|
) -> ModuleResponse:
|
||||||
|
"""Build ModuleResponse from module code."""
|
||||||
|
from models.database.admin_menu_config import FrontendType
|
||||||
|
|
||||||
|
module = MODULES.get(code)
|
||||||
|
if not module:
|
||||||
|
raise ValueError(f"Unknown module: {code}")
|
||||||
|
|
||||||
|
return ModuleResponse(
|
||||||
|
code=module.code,
|
||||||
|
name=module.name,
|
||||||
|
description=module.description,
|
||||||
|
is_core=module.is_core,
|
||||||
|
is_enabled=is_enabled,
|
||||||
|
requires=module.requires,
|
||||||
|
features=module.features,
|
||||||
|
menu_items_admin=module.get_menu_items(FrontendType.ADMIN),
|
||||||
|
menu_items_vendor=module.get_menu_items(FrontendType.VENDOR),
|
||||||
|
dependent_modules=_get_dependent_modules(module.code),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# API Endpoints
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("", response_model=ModuleListResponse)
|
||||||
|
async def list_all_modules(
|
||||||
|
current_user: User = Depends(get_current_super_admin),
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
List all available modules.
|
||||||
|
|
||||||
|
Returns all module definitions with their metadata.
|
||||||
|
Super admin only.
|
||||||
|
"""
|
||||||
|
modules = []
|
||||||
|
for code in MODULES.keys():
|
||||||
|
# All modules shown as enabled in the global list
|
||||||
|
modules.append(_build_module_response(code, is_enabled=True))
|
||||||
|
|
||||||
|
# Sort: core first, then alphabetically
|
||||||
|
modules.sort(key=lambda m: (not m.is_core, m.name))
|
||||||
|
|
||||||
|
logger.info(f"[MODULES] Super admin {current_user.email} listed all modules")
|
||||||
|
|
||||||
|
return ModuleListResponse(
|
||||||
|
modules=modules,
|
||||||
|
total=len(modules),
|
||||||
|
enabled=len(modules),
|
||||||
|
disabled=0,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/platforms/{platform_id}", response_model=PlatformModulesResponse)
|
||||||
|
async def get_platform_modules(
|
||||||
|
platform_id: int = Path(..., description="Platform ID"),
|
||||||
|
db: Session = Depends(get_db),
|
||||||
|
current_user: User = Depends(get_current_super_admin),
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Get module configuration for a platform.
|
||||||
|
|
||||||
|
Returns all modules with their enablement status for the platform.
|
||||||
|
Super admin only.
|
||||||
|
"""
|
||||||
|
# Verify platform exists
|
||||||
|
platform = platform_service.get_platform_by_id(db, platform_id)
|
||||||
|
|
||||||
|
# Get enabled module codes for this platform
|
||||||
|
enabled_codes = module_service.get_enabled_module_codes(db, platform_id)
|
||||||
|
|
||||||
|
modules = []
|
||||||
|
for code in MODULES.keys():
|
||||||
|
is_enabled = code in enabled_codes
|
||||||
|
modules.append(_build_module_response(code, is_enabled))
|
||||||
|
|
||||||
|
# Sort: core first, then alphabetically
|
||||||
|
modules.sort(key=lambda m: (not m.is_core, m.name))
|
||||||
|
|
||||||
|
enabled_count = sum(1 for m in modules if m.is_enabled)
|
||||||
|
|
||||||
|
logger.info(
|
||||||
|
f"[MODULES] Super admin {current_user.email} fetched modules "
|
||||||
|
f"for platform {platform.code} ({enabled_count}/{len(modules)} enabled)"
|
||||||
|
)
|
||||||
|
|
||||||
|
return PlatformModulesResponse(
|
||||||
|
platform_id=platform.id,
|
||||||
|
platform_code=platform.code,
|
||||||
|
platform_name=platform.name,
|
||||||
|
modules=modules,
|
||||||
|
total=len(modules),
|
||||||
|
enabled=enabled_count,
|
||||||
|
disabled=len(modules) - enabled_count,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@router.put("/platforms/{platform_id}", response_model=PlatformModulesResponse)
|
||||||
|
async def update_platform_modules(
|
||||||
|
update_data: EnableModulesRequest,
|
||||||
|
platform_id: int = Path(..., description="Platform ID"),
|
||||||
|
db: Session = Depends(get_db),
|
||||||
|
current_user: User = Depends(get_current_super_admin),
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Update enabled modules for a platform.
|
||||||
|
|
||||||
|
Sets the list of enabled modules. Core modules are automatically included.
|
||||||
|
Dependencies are automatically resolved.
|
||||||
|
Super admin only.
|
||||||
|
"""
|
||||||
|
# Verify platform exists
|
||||||
|
platform = platform_service.get_platform_by_id(db, platform_id)
|
||||||
|
|
||||||
|
# Update enabled modules
|
||||||
|
module_service.set_enabled_modules(db, platform_id, update_data.module_codes)
|
||||||
|
db.commit()
|
||||||
|
|
||||||
|
# Get updated module list
|
||||||
|
enabled_codes = module_service.get_enabled_module_codes(db, platform_id)
|
||||||
|
|
||||||
|
modules = []
|
||||||
|
for code in MODULES.keys():
|
||||||
|
is_enabled = code in enabled_codes
|
||||||
|
modules.append(_build_module_response(code, is_enabled))
|
||||||
|
|
||||||
|
modules.sort(key=lambda m: (not m.is_core, m.name))
|
||||||
|
enabled_count = sum(1 for m in modules if m.is_enabled)
|
||||||
|
|
||||||
|
logger.info(
|
||||||
|
f"[MODULES] Super admin {current_user.email} updated modules "
|
||||||
|
f"for platform {platform.code}: {sorted(update_data.module_codes)}"
|
||||||
|
)
|
||||||
|
|
||||||
|
return PlatformModulesResponse(
|
||||||
|
platform_id=platform.id,
|
||||||
|
platform_code=platform.code,
|
||||||
|
platform_name=platform.name,
|
||||||
|
modules=modules,
|
||||||
|
total=len(modules),
|
||||||
|
enabled=enabled_count,
|
||||||
|
disabled=len(modules) - enabled_count,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/platforms/{platform_id}/enable")
|
||||||
|
async def enable_module(
|
||||||
|
request: ToggleModuleRequest,
|
||||||
|
platform_id: int = Path(..., description="Platform ID"),
|
||||||
|
db: Session = Depends(get_db),
|
||||||
|
current_user: User = Depends(get_current_super_admin),
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Enable a single module for a platform.
|
||||||
|
|
||||||
|
Also enables required dependencies.
|
||||||
|
Super admin only.
|
||||||
|
"""
|
||||||
|
# Verify platform exists
|
||||||
|
platform = platform_service.get_platform_by_id(db, platform_id)
|
||||||
|
|
||||||
|
# Validate module code
|
||||||
|
if request.module_code not in MODULES:
|
||||||
|
from app.exceptions import BadRequestException
|
||||||
|
|
||||||
|
raise BadRequestException(f"Unknown module: {request.module_code}")
|
||||||
|
|
||||||
|
# Enable module
|
||||||
|
success = module_service.enable_module(db, platform_id, request.module_code)
|
||||||
|
if success:
|
||||||
|
db.commit()
|
||||||
|
|
||||||
|
# Check what dependencies were also enabled
|
||||||
|
module = MODULES[request.module_code]
|
||||||
|
enabled_deps = module.requires if module.requires else []
|
||||||
|
|
||||||
|
logger.info(
|
||||||
|
f"[MODULES] Super admin {current_user.email} enabled module "
|
||||||
|
f"'{request.module_code}' for platform {platform.code}"
|
||||||
|
)
|
||||||
|
|
||||||
|
return {
|
||||||
|
"success": success,
|
||||||
|
"message": f"Module '{request.module_code}' enabled",
|
||||||
|
"also_enabled": enabled_deps,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/platforms/{platform_id}/disable")
|
||||||
|
async def disable_module(
|
||||||
|
request: ToggleModuleRequest,
|
||||||
|
platform_id: int = Path(..., description="Platform ID"),
|
||||||
|
db: Session = Depends(get_db),
|
||||||
|
current_user: User = Depends(get_current_super_admin),
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Disable a single module for a platform.
|
||||||
|
|
||||||
|
Core modules cannot be disabled.
|
||||||
|
Also disables modules that depend on this one.
|
||||||
|
Super admin only.
|
||||||
|
"""
|
||||||
|
# Verify platform exists
|
||||||
|
platform = platform_service.get_platform_by_id(db, platform_id)
|
||||||
|
|
||||||
|
# Validate module code
|
||||||
|
if request.module_code not in MODULES:
|
||||||
|
from app.exceptions import BadRequestException
|
||||||
|
|
||||||
|
raise BadRequestException(f"Unknown module: {request.module_code}")
|
||||||
|
|
||||||
|
# Check if core module
|
||||||
|
if request.module_code in get_core_module_codes():
|
||||||
|
from app.exceptions import BadRequestException
|
||||||
|
|
||||||
|
raise BadRequestException(f"Cannot disable core module: {request.module_code}")
|
||||||
|
|
||||||
|
# Get dependent modules before disabling
|
||||||
|
dependents = _get_dependent_modules(request.module_code)
|
||||||
|
|
||||||
|
# Disable module
|
||||||
|
success = module_service.disable_module(db, platform_id, request.module_code)
|
||||||
|
if success:
|
||||||
|
db.commit()
|
||||||
|
|
||||||
|
logger.info(
|
||||||
|
f"[MODULES] Super admin {current_user.email} disabled module "
|
||||||
|
f"'{request.module_code}' for platform {platform.code}"
|
||||||
|
)
|
||||||
|
|
||||||
|
return {
|
||||||
|
"success": success,
|
||||||
|
"message": f"Module '{request.module_code}' disabled",
|
||||||
|
"also_disabled": dependents if dependents else [],
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/platforms/{platform_id}/enable-all")
|
||||||
|
async def enable_all_modules(
|
||||||
|
platform_id: int = Path(..., description="Platform ID"),
|
||||||
|
db: Session = Depends(get_db),
|
||||||
|
current_user: User = Depends(get_current_super_admin),
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Enable all modules for a platform.
|
||||||
|
|
||||||
|
Super admin only.
|
||||||
|
"""
|
||||||
|
# Verify platform exists
|
||||||
|
platform = platform_service.get_platform_by_id(db, platform_id)
|
||||||
|
|
||||||
|
# Enable all modules
|
||||||
|
all_codes = list(MODULES.keys())
|
||||||
|
module_service.set_enabled_modules(db, platform_id, all_codes)
|
||||||
|
db.commit()
|
||||||
|
|
||||||
|
logger.info(
|
||||||
|
f"[MODULES] Super admin {current_user.email} enabled all modules "
|
||||||
|
f"for platform {platform.code}"
|
||||||
|
)
|
||||||
|
|
||||||
|
return {
|
||||||
|
"success": True,
|
||||||
|
"message": "All modules enabled",
|
||||||
|
"enabled_count": len(all_codes),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/platforms/{platform_id}/disable-optional")
|
||||||
|
async def disable_optional_modules(
|
||||||
|
platform_id: int = Path(..., description="Platform ID"),
|
||||||
|
db: Session = Depends(get_db),
|
||||||
|
current_user: User = Depends(get_current_super_admin),
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Disable all optional modules for a platform, keeping only core modules.
|
||||||
|
|
||||||
|
Super admin only.
|
||||||
|
"""
|
||||||
|
# Verify platform exists
|
||||||
|
platform = platform_service.get_platform_by_id(db, platform_id)
|
||||||
|
|
||||||
|
# Enable only core modules
|
||||||
|
core_codes = list(get_core_module_codes())
|
||||||
|
module_service.set_enabled_modules(db, platform_id, core_codes)
|
||||||
|
db.commit()
|
||||||
|
|
||||||
|
logger.info(
|
||||||
|
f"[MODULES] Super admin {current_user.email} disabled optional modules "
|
||||||
|
f"for platform {platform.code} (kept {len(core_codes)} core modules)"
|
||||||
|
)
|
||||||
|
|
||||||
|
return {
|
||||||
|
"success": True,
|
||||||
|
"message": "Optional modules disabled, core modules kept",
|
||||||
|
"core_modules": core_codes,
|
||||||
|
}
|
||||||
@@ -48,11 +48,12 @@ from fastapi.templating import Jinja2Templates
|
|||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
|
|
||||||
from app.api.deps import (
|
from app.api.deps import (
|
||||||
get_current_admin_from_cookie_or_header,
|
|
||||||
get_current_admin_optional,
|
get_current_admin_optional,
|
||||||
get_db,
|
get_db,
|
||||||
|
require_menu_access,
|
||||||
)
|
)
|
||||||
from app.core.config import settings
|
from app.core.config import settings
|
||||||
|
from models.database.admin_menu_config import FrontendType
|
||||||
from models.database.user import User
|
from models.database.user import User
|
||||||
|
|
||||||
router = APIRouter()
|
router = APIRouter()
|
||||||
@@ -132,7 +133,7 @@ async def admin_select_platform_page(
|
|||||||
@router.get("/dashboard", response_class=HTMLResponse, include_in_schema=False)
|
@router.get("/dashboard", response_class=HTMLResponse, include_in_schema=False)
|
||||||
async def admin_dashboard_page(
|
async def admin_dashboard_page(
|
||||||
request: Request,
|
request: Request,
|
||||||
current_user: User = Depends(get_current_admin_from_cookie_or_header),
|
current_user: User = Depends(require_menu_access("dashboard", FrontendType.ADMIN)),
|
||||||
db: Session = Depends(get_db),
|
db: Session = Depends(get_db),
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
@@ -156,7 +157,7 @@ async def admin_dashboard_page(
|
|||||||
@router.get("/companies", response_class=HTMLResponse, include_in_schema=False)
|
@router.get("/companies", response_class=HTMLResponse, include_in_schema=False)
|
||||||
async def admin_companies_list_page(
|
async def admin_companies_list_page(
|
||||||
request: Request,
|
request: Request,
|
||||||
current_user: User = Depends(get_current_admin_from_cookie_or_header),
|
current_user: User = Depends(require_menu_access("companies", FrontendType.ADMIN)),
|
||||||
db: Session = Depends(get_db),
|
db: Session = Depends(get_db),
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
@@ -175,7 +176,7 @@ async def admin_companies_list_page(
|
|||||||
@router.get("/companies/create", response_class=HTMLResponse, include_in_schema=False)
|
@router.get("/companies/create", response_class=HTMLResponse, include_in_schema=False)
|
||||||
async def admin_company_create_page(
|
async def admin_company_create_page(
|
||||||
request: Request,
|
request: Request,
|
||||||
current_user: User = Depends(get_current_admin_from_cookie_or_header),
|
current_user: User = Depends(require_menu_access("companies", FrontendType.ADMIN)),
|
||||||
db: Session = Depends(get_db),
|
db: Session = Depends(get_db),
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
@@ -196,7 +197,7 @@ async def admin_company_create_page(
|
|||||||
async def admin_company_detail_page(
|
async def admin_company_detail_page(
|
||||||
request: Request,
|
request: Request,
|
||||||
company_id: int = Path(..., description="Company ID"),
|
company_id: int = Path(..., description="Company ID"),
|
||||||
current_user: User = Depends(get_current_admin_from_cookie_or_header),
|
current_user: User = Depends(require_menu_access("companies", FrontendType.ADMIN)),
|
||||||
db: Session = Depends(get_db),
|
db: Session = Depends(get_db),
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
@@ -218,7 +219,7 @@ async def admin_company_detail_page(
|
|||||||
async def admin_company_edit_page(
|
async def admin_company_edit_page(
|
||||||
request: Request,
|
request: Request,
|
||||||
company_id: int = Path(..., description="Company ID"),
|
company_id: int = Path(..., description="Company ID"),
|
||||||
current_user: User = Depends(get_current_admin_from_cookie_or_header),
|
current_user: User = Depends(require_menu_access("companies", FrontendType.ADMIN)),
|
||||||
db: Session = Depends(get_db),
|
db: Session = Depends(get_db),
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
@@ -242,7 +243,7 @@ async def admin_company_edit_page(
|
|||||||
@router.get("/vendors", response_class=HTMLResponse, include_in_schema=False)
|
@router.get("/vendors", response_class=HTMLResponse, include_in_schema=False)
|
||||||
async def admin_vendors_list_page(
|
async def admin_vendors_list_page(
|
||||||
request: Request,
|
request: Request,
|
||||||
current_user: User = Depends(get_current_admin_from_cookie_or_header),
|
current_user: User = Depends(require_menu_access("vendors", FrontendType.ADMIN)),
|
||||||
db: Session = Depends(get_db),
|
db: Session = Depends(get_db),
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
@@ -261,7 +262,7 @@ async def admin_vendors_list_page(
|
|||||||
@router.get("/vendors/create", response_class=HTMLResponse, include_in_schema=False)
|
@router.get("/vendors/create", response_class=HTMLResponse, include_in_schema=False)
|
||||||
async def admin_vendor_create_page(
|
async def admin_vendor_create_page(
|
||||||
request: Request,
|
request: Request,
|
||||||
current_user: User = Depends(get_current_admin_from_cookie_or_header),
|
current_user: User = Depends(require_menu_access("vendors", FrontendType.ADMIN)),
|
||||||
db: Session = Depends(get_db),
|
db: Session = Depends(get_db),
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
@@ -282,7 +283,7 @@ async def admin_vendor_create_page(
|
|||||||
async def admin_vendor_detail_page(
|
async def admin_vendor_detail_page(
|
||||||
request: Request,
|
request: Request,
|
||||||
vendor_code: str = Path(..., description="Vendor code"),
|
vendor_code: str = Path(..., description="Vendor code"),
|
||||||
current_user: User = Depends(get_current_admin_from_cookie_or_header),
|
current_user: User = Depends(require_menu_access("vendors", FrontendType.ADMIN)),
|
||||||
db: Session = Depends(get_db),
|
db: Session = Depends(get_db),
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
@@ -305,7 +306,7 @@ async def admin_vendor_detail_page(
|
|||||||
async def admin_vendor_edit_page(
|
async def admin_vendor_edit_page(
|
||||||
request: Request,
|
request: Request,
|
||||||
vendor_code: str = Path(..., description="Vendor code"),
|
vendor_code: str = Path(..., description="Vendor code"),
|
||||||
current_user: User = Depends(get_current_admin_from_cookie_or_header),
|
current_user: User = Depends(require_menu_access("vendors", FrontendType.ADMIN)),
|
||||||
db: Session = Depends(get_db),
|
db: Session = Depends(get_db),
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
@@ -334,7 +335,7 @@ async def admin_vendor_edit_page(
|
|||||||
async def admin_vendor_domains_page(
|
async def admin_vendor_domains_page(
|
||||||
request: Request,
|
request: Request,
|
||||||
vendor_code: str = Path(..., description="Vendor code"),
|
vendor_code: str = Path(..., description="Vendor code"),
|
||||||
current_user: User = Depends(get_current_admin_from_cookie_or_header),
|
current_user: User = Depends(require_menu_access("vendors", FrontendType.ADMIN)),
|
||||||
db: Session = Depends(get_db),
|
db: Session = Depends(get_db),
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
@@ -359,7 +360,7 @@ async def admin_vendor_domains_page(
|
|||||||
@router.get("/vendor-themes", response_class=HTMLResponse, include_in_schema=False)
|
@router.get("/vendor-themes", response_class=HTMLResponse, include_in_schema=False)
|
||||||
async def admin_vendor_themes_page(
|
async def admin_vendor_themes_page(
|
||||||
request: Request,
|
request: Request,
|
||||||
current_user: User = Depends(get_current_admin_from_cookie_or_header),
|
current_user: User = Depends(require_menu_access("vendor-themes", FrontendType.ADMIN)),
|
||||||
db: Session = Depends(get_db),
|
db: Session = Depends(get_db),
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
@@ -381,7 +382,7 @@ async def admin_vendor_themes_page(
|
|||||||
async def admin_vendor_theme_page(
|
async def admin_vendor_theme_page(
|
||||||
request: Request,
|
request: Request,
|
||||||
vendor_code: str = Path(..., description="Vendor code"),
|
vendor_code: str = Path(..., description="Vendor code"),
|
||||||
current_user: User = Depends(get_current_admin_from_cookie_or_header),
|
current_user: User = Depends(require_menu_access("vendor-themes", FrontendType.ADMIN)),
|
||||||
db: Session = Depends(get_db),
|
db: Session = Depends(get_db),
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
@@ -406,18 +407,14 @@ async def admin_vendor_theme_page(
|
|||||||
@router.get("/admin-users", response_class=HTMLResponse, include_in_schema=False)
|
@router.get("/admin-users", response_class=HTMLResponse, include_in_schema=False)
|
||||||
async def admin_users_list_page(
|
async def admin_users_list_page(
|
||||||
request: Request,
|
request: Request,
|
||||||
current_user: User = Depends(get_current_admin_from_cookie_or_header),
|
current_user: User = Depends(require_menu_access("admin-users", FrontendType.ADMIN)),
|
||||||
db: Session = Depends(get_db),
|
db: Session = Depends(get_db),
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
Render admin users management page.
|
Render admin users management page.
|
||||||
Shows list of all admin users (super admins and platform admins).
|
Shows list of all admin users (super admins and platform admins).
|
||||||
Super admin only.
|
Super admin only (menu is in super_admin_only section).
|
||||||
"""
|
"""
|
||||||
from fastapi import HTTPException
|
|
||||||
|
|
||||||
if not current_user.is_super_admin:
|
|
||||||
raise HTTPException(status_code=403, detail="Super admin access required")
|
|
||||||
|
|
||||||
return templates.TemplateResponse(
|
return templates.TemplateResponse(
|
||||||
"admin/admin-users.html",
|
"admin/admin-users.html",
|
||||||
@@ -431,17 +428,13 @@ async def admin_users_list_page(
|
|||||||
@router.get("/admin-users/create", response_class=HTMLResponse, include_in_schema=False)
|
@router.get("/admin-users/create", response_class=HTMLResponse, include_in_schema=False)
|
||||||
async def admin_user_create_page(
|
async def admin_user_create_page(
|
||||||
request: Request,
|
request: Request,
|
||||||
current_user: User = Depends(get_current_admin_from_cookie_or_header),
|
current_user: User = Depends(require_menu_access("admin-users", FrontendType.ADMIN)),
|
||||||
db: Session = Depends(get_db),
|
db: Session = Depends(get_db),
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
Render admin user creation form.
|
Render admin user creation form.
|
||||||
Super admin only.
|
Super admin only (menu is in super_admin_only section).
|
||||||
"""
|
"""
|
||||||
from fastapi import HTTPException
|
|
||||||
|
|
||||||
if not current_user.is_super_admin:
|
|
||||||
raise HTTPException(status_code=403, detail="Super admin access required")
|
|
||||||
|
|
||||||
return templates.TemplateResponse(
|
return templates.TemplateResponse(
|
||||||
"admin/user-create.html",
|
"admin/user-create.html",
|
||||||
@@ -458,17 +451,13 @@ async def admin_user_create_page(
|
|||||||
async def admin_user_detail_page(
|
async def admin_user_detail_page(
|
||||||
request: Request,
|
request: Request,
|
||||||
user_id: int = Path(..., description="User ID"),
|
user_id: int = Path(..., description="User ID"),
|
||||||
current_user: User = Depends(get_current_admin_from_cookie_or_header),
|
current_user: User = Depends(require_menu_access("admin-users", FrontendType.ADMIN)),
|
||||||
db: Session = Depends(get_db),
|
db: Session = Depends(get_db),
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
Render admin user detail view.
|
Render admin user detail view.
|
||||||
Super admin only.
|
Super admin only (menu is in super_admin_only section).
|
||||||
"""
|
"""
|
||||||
from fastapi import HTTPException
|
|
||||||
|
|
||||||
if not current_user.is_super_admin:
|
|
||||||
raise HTTPException(status_code=403, detail="Super admin access required")
|
|
||||||
|
|
||||||
return templates.TemplateResponse(
|
return templates.TemplateResponse(
|
||||||
"admin/admin-user-detail.html",
|
"admin/admin-user-detail.html",
|
||||||
@@ -486,17 +475,13 @@ async def admin_user_detail_page(
|
|||||||
async def admin_user_edit_page(
|
async def admin_user_edit_page(
|
||||||
request: Request,
|
request: Request,
|
||||||
user_id: int = Path(..., description="User ID"),
|
user_id: int = Path(..., description="User ID"),
|
||||||
current_user: User = Depends(get_current_admin_from_cookie_or_header),
|
current_user: User = Depends(require_menu_access("admin-users", FrontendType.ADMIN)),
|
||||||
db: Session = Depends(get_db),
|
db: Session = Depends(get_db),
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
Render admin user edit form.
|
Render admin user edit form.
|
||||||
Super admin only.
|
Super admin only (menu is in super_admin_only section).
|
||||||
"""
|
"""
|
||||||
from fastapi import HTTPException
|
|
||||||
|
|
||||||
if not current_user.is_super_admin:
|
|
||||||
raise HTTPException(status_code=403, detail="Super admin access required")
|
|
||||||
|
|
||||||
return templates.TemplateResponse(
|
return templates.TemplateResponse(
|
||||||
"admin/admin-user-edit.html",
|
"admin/admin-user-edit.html",
|
||||||
@@ -557,7 +542,7 @@ async def admin_user_edit_page_redirect(user_id: int = Path(..., description="Us
|
|||||||
@router.get("/customers", response_class=HTMLResponse, include_in_schema=False)
|
@router.get("/customers", response_class=HTMLResponse, include_in_schema=False)
|
||||||
async def admin_customers_page(
|
async def admin_customers_page(
|
||||||
request: Request,
|
request: Request,
|
||||||
current_user: User = Depends(get_current_admin_from_cookie_or_header),
|
current_user: User = Depends(require_menu_access("customers", FrontendType.ADMIN)),
|
||||||
db: Session = Depends(get_db),
|
db: Session = Depends(get_db),
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
@@ -581,7 +566,7 @@ async def admin_customers_page(
|
|||||||
@router.get("/notifications", response_class=HTMLResponse, include_in_schema=False)
|
@router.get("/notifications", response_class=HTMLResponse, include_in_schema=False)
|
||||||
async def admin_notifications_page(
|
async def admin_notifications_page(
|
||||||
request: Request,
|
request: Request,
|
||||||
current_user: User = Depends(get_current_admin_from_cookie_or_header),
|
current_user: User = Depends(require_menu_access("notifications", FrontendType.ADMIN)),
|
||||||
db: Session = Depends(get_db),
|
db: Session = Depends(get_db),
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
@@ -605,7 +590,7 @@ async def admin_notifications_page(
|
|||||||
@router.get("/email-templates", response_class=HTMLResponse, include_in_schema=False)
|
@router.get("/email-templates", response_class=HTMLResponse, include_in_schema=False)
|
||||||
async def admin_email_templates_page(
|
async def admin_email_templates_page(
|
||||||
request: Request,
|
request: Request,
|
||||||
current_user: User = Depends(get_current_admin_from_cookie_or_header),
|
current_user: User = Depends(require_menu_access("email-templates", FrontendType.ADMIN)),
|
||||||
db: Session = Depends(get_db),
|
db: Session = Depends(get_db),
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
@@ -629,7 +614,7 @@ async def admin_email_templates_page(
|
|||||||
@router.get("/messages", response_class=HTMLResponse, include_in_schema=False)
|
@router.get("/messages", response_class=HTMLResponse, include_in_schema=False)
|
||||||
async def admin_messages_page(
|
async def admin_messages_page(
|
||||||
request: Request,
|
request: Request,
|
||||||
current_user: User = Depends(get_current_admin_from_cookie_or_header),
|
current_user: User = Depends(require_menu_access("messages", FrontendType.ADMIN)),
|
||||||
db: Session = Depends(get_db),
|
db: Session = Depends(get_db),
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
@@ -653,7 +638,7 @@ async def admin_messages_page(
|
|||||||
async def admin_conversation_detail_page(
|
async def admin_conversation_detail_page(
|
||||||
request: Request,
|
request: Request,
|
||||||
conversation_id: int = Path(..., description="Conversation ID"),
|
conversation_id: int = Path(..., description="Conversation ID"),
|
||||||
current_user: User = Depends(get_current_admin_from_cookie_or_header),
|
current_user: User = Depends(require_menu_access("messages", FrontendType.ADMIN)),
|
||||||
db: Session = Depends(get_db),
|
db: Session = Depends(get_db),
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
@@ -678,7 +663,7 @@ async def admin_conversation_detail_page(
|
|||||||
@router.get("/inventory", response_class=HTMLResponse, include_in_schema=False)
|
@router.get("/inventory", response_class=HTMLResponse, include_in_schema=False)
|
||||||
async def admin_inventory_page(
|
async def admin_inventory_page(
|
||||||
request: Request,
|
request: Request,
|
||||||
current_user: User = Depends(get_current_admin_from_cookie_or_header),
|
current_user: User = Depends(require_menu_access("inventory", FrontendType.ADMIN)),
|
||||||
db: Session = Depends(get_db),
|
db: Session = Depends(get_db),
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
@@ -702,7 +687,7 @@ async def admin_inventory_page(
|
|||||||
@router.get("/orders", response_class=HTMLResponse, include_in_schema=False)
|
@router.get("/orders", response_class=HTMLResponse, include_in_schema=False)
|
||||||
async def admin_orders_page(
|
async def admin_orders_page(
|
||||||
request: Request,
|
request: Request,
|
||||||
current_user: User = Depends(get_current_admin_from_cookie_or_header),
|
current_user: User = Depends(require_menu_access("orders", FrontendType.ADMIN)),
|
||||||
db: Session = Depends(get_db),
|
db: Session = Depends(get_db),
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
@@ -726,7 +711,7 @@ async def admin_orders_page(
|
|||||||
@router.get("/imports", response_class=HTMLResponse, include_in_schema=False)
|
@router.get("/imports", response_class=HTMLResponse, include_in_schema=False)
|
||||||
async def admin_imports_page(
|
async def admin_imports_page(
|
||||||
request: Request,
|
request: Request,
|
||||||
current_user: User = Depends(get_current_admin_from_cookie_or_header),
|
current_user: User = Depends(require_menu_access("imports", FrontendType.ADMIN)),
|
||||||
db: Session = Depends(get_db),
|
db: Session = Depends(get_db),
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
@@ -745,7 +730,7 @@ async def admin_imports_page(
|
|||||||
@router.get("/background-tasks", response_class=HTMLResponse, include_in_schema=False)
|
@router.get("/background-tasks", response_class=HTMLResponse, include_in_schema=False)
|
||||||
async def admin_background_tasks_page(
|
async def admin_background_tasks_page(
|
||||||
request: Request,
|
request: Request,
|
||||||
current_user: User = Depends(get_current_admin_from_cookie_or_header),
|
current_user: User = Depends(require_menu_access("background-tasks", FrontendType.ADMIN)),
|
||||||
db: Session = Depends(get_db),
|
db: Session = Depends(get_db),
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
@@ -765,7 +750,7 @@ async def admin_background_tasks_page(
|
|||||||
@router.get("/marketplace", response_class=HTMLResponse, include_in_schema=False)
|
@router.get("/marketplace", response_class=HTMLResponse, include_in_schema=False)
|
||||||
async def admin_marketplace_page(
|
async def admin_marketplace_page(
|
||||||
request: Request,
|
request: Request,
|
||||||
current_user: User = Depends(get_current_admin_from_cookie_or_header),
|
current_user: User = Depends(require_menu_access("marketplace-letzshop", FrontendType.ADMIN)),
|
||||||
db: Session = Depends(get_db),
|
db: Session = Depends(get_db),
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
@@ -791,7 +776,7 @@ async def admin_marketplace_page(
|
|||||||
)
|
)
|
||||||
async def admin_marketplace_letzshop_page(
|
async def admin_marketplace_letzshop_page(
|
||||||
request: Request,
|
request: Request,
|
||||||
current_user: User = Depends(get_current_admin_from_cookie_or_header),
|
current_user: User = Depends(require_menu_access("marketplace-letzshop", FrontendType.ADMIN)),
|
||||||
db: Session = Depends(get_db),
|
db: Session = Depends(get_db),
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
@@ -814,7 +799,7 @@ async def admin_marketplace_letzshop_page(
|
|||||||
async def admin_letzshop_order_detail_page(
|
async def admin_letzshop_order_detail_page(
|
||||||
request: Request,
|
request: Request,
|
||||||
order_id: int = Path(..., description="Letzshop order ID"),
|
order_id: int = Path(..., description="Letzshop order ID"),
|
||||||
current_user: User = Depends(get_current_admin_from_cookie_or_header),
|
current_user: User = Depends(require_menu_access("marketplace-letzshop", FrontendType.ADMIN)),
|
||||||
db: Session = Depends(get_db),
|
db: Session = Depends(get_db),
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
@@ -840,7 +825,7 @@ async def admin_letzshop_order_detail_page(
|
|||||||
async def admin_letzshop_product_detail_page(
|
async def admin_letzshop_product_detail_page(
|
||||||
request: Request,
|
request: Request,
|
||||||
product_id: int = Path(..., description="Marketplace Product ID"),
|
product_id: int = Path(..., description="Marketplace Product ID"),
|
||||||
current_user: User = Depends(get_current_admin_from_cookie_or_header),
|
current_user: User = Depends(require_menu_access("marketplace-letzshop", FrontendType.ADMIN)),
|
||||||
db: Session = Depends(get_db),
|
db: Session = Depends(get_db),
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
@@ -870,7 +855,7 @@ async def admin_letzshop_product_detail_page(
|
|||||||
)
|
)
|
||||||
async def admin_letzshop_vendor_directory_page(
|
async def admin_letzshop_vendor_directory_page(
|
||||||
request: Request,
|
request: Request,
|
||||||
current_user: User = Depends(get_current_admin_from_cookie_or_header),
|
current_user: User = Depends(require_menu_access("marketplace-letzshop", FrontendType.ADMIN)),
|
||||||
db: Session = Depends(get_db),
|
db: Session = Depends(get_db),
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
@@ -900,7 +885,7 @@ async def admin_letzshop_vendor_directory_page(
|
|||||||
)
|
)
|
||||||
async def admin_marketplace_products_page(
|
async def admin_marketplace_products_page(
|
||||||
request: Request,
|
request: Request,
|
||||||
current_user: User = Depends(get_current_admin_from_cookie_or_header),
|
current_user: User = Depends(require_menu_access("marketplace-letzshop", FrontendType.ADMIN)),
|
||||||
db: Session = Depends(get_db),
|
db: Session = Depends(get_db),
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
@@ -924,7 +909,7 @@ async def admin_marketplace_products_page(
|
|||||||
async def admin_marketplace_product_detail_page(
|
async def admin_marketplace_product_detail_page(
|
||||||
request: Request,
|
request: Request,
|
||||||
product_id: int = Path(..., description="Marketplace Product ID"),
|
product_id: int = Path(..., description="Marketplace Product ID"),
|
||||||
current_user: User = Depends(get_current_admin_from_cookie_or_header),
|
current_user: User = Depends(require_menu_access("marketplace-letzshop", FrontendType.ADMIN)),
|
||||||
db: Session = Depends(get_db),
|
db: Session = Depends(get_db),
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
@@ -945,7 +930,7 @@ async def admin_marketplace_product_detail_page(
|
|||||||
@router.get("/vendor-products", response_class=HTMLResponse, include_in_schema=False)
|
@router.get("/vendor-products", response_class=HTMLResponse, include_in_schema=False)
|
||||||
async def admin_vendor_products_page(
|
async def admin_vendor_products_page(
|
||||||
request: Request,
|
request: Request,
|
||||||
current_user: User = Depends(get_current_admin_from_cookie_or_header),
|
current_user: User = Depends(require_menu_access("vendor-products", FrontendType.ADMIN)),
|
||||||
db: Session = Depends(get_db),
|
db: Session = Depends(get_db),
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
@@ -964,7 +949,7 @@ async def admin_vendor_products_page(
|
|||||||
@router.get("/vendor-products/create", response_class=HTMLResponse, include_in_schema=False)
|
@router.get("/vendor-products/create", response_class=HTMLResponse, include_in_schema=False)
|
||||||
async def admin_vendor_product_create_page(
|
async def admin_vendor_product_create_page(
|
||||||
request: Request,
|
request: Request,
|
||||||
current_user: User = Depends(get_current_admin_from_cookie_or_header),
|
current_user: User = Depends(require_menu_access("vendor-products", FrontendType.ADMIN)),
|
||||||
db: Session = Depends(get_db),
|
db: Session = Depends(get_db),
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
@@ -988,7 +973,7 @@ async def admin_vendor_product_create_page(
|
|||||||
async def admin_vendor_product_detail_page(
|
async def admin_vendor_product_detail_page(
|
||||||
request: Request,
|
request: Request,
|
||||||
product_id: int = Path(..., description="Vendor Product ID"),
|
product_id: int = Path(..., description="Vendor Product ID"),
|
||||||
current_user: User = Depends(get_current_admin_from_cookie_or_header),
|
current_user: User = Depends(require_menu_access("vendor-products", FrontendType.ADMIN)),
|
||||||
db: Session = Depends(get_db),
|
db: Session = Depends(get_db),
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
@@ -1013,7 +998,7 @@ async def admin_vendor_product_detail_page(
|
|||||||
async def admin_vendor_product_edit_page(
|
async def admin_vendor_product_edit_page(
|
||||||
request: Request,
|
request: Request,
|
||||||
product_id: int = Path(..., description="Vendor Product ID"),
|
product_id: int = Path(..., description="Vendor Product ID"),
|
||||||
current_user: User = Depends(get_current_admin_from_cookie_or_header),
|
current_user: User = Depends(require_menu_access("vendor-products", FrontendType.ADMIN)),
|
||||||
db: Session = Depends(get_db),
|
db: Session = Depends(get_db),
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
@@ -1038,7 +1023,7 @@ async def admin_vendor_product_edit_page(
|
|||||||
@router.get("/subscription-tiers", response_class=HTMLResponse, include_in_schema=False)
|
@router.get("/subscription-tiers", response_class=HTMLResponse, include_in_schema=False)
|
||||||
async def admin_subscription_tiers_page(
|
async def admin_subscription_tiers_page(
|
||||||
request: Request,
|
request: Request,
|
||||||
current_user: User = Depends(get_current_admin_from_cookie_or_header),
|
current_user: User = Depends(require_menu_access("subscription-tiers", FrontendType.ADMIN)),
|
||||||
db: Session = Depends(get_db),
|
db: Session = Depends(get_db),
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
@@ -1057,7 +1042,7 @@ async def admin_subscription_tiers_page(
|
|||||||
@router.get("/subscriptions", response_class=HTMLResponse, include_in_schema=False)
|
@router.get("/subscriptions", response_class=HTMLResponse, include_in_schema=False)
|
||||||
async def admin_subscriptions_page(
|
async def admin_subscriptions_page(
|
||||||
request: Request,
|
request: Request,
|
||||||
current_user: User = Depends(get_current_admin_from_cookie_or_header),
|
current_user: User = Depends(require_menu_access("subscriptions", FrontendType.ADMIN)),
|
||||||
db: Session = Depends(get_db),
|
db: Session = Depends(get_db),
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
@@ -1076,7 +1061,7 @@ async def admin_subscriptions_page(
|
|||||||
@router.get("/billing-history", response_class=HTMLResponse, include_in_schema=False)
|
@router.get("/billing-history", response_class=HTMLResponse, include_in_schema=False)
|
||||||
async def admin_billing_history_page(
|
async def admin_billing_history_page(
|
||||||
request: Request,
|
request: Request,
|
||||||
current_user: User = Depends(get_current_admin_from_cookie_or_header),
|
current_user: User = Depends(require_menu_access("billing-history", FrontendType.ADMIN)),
|
||||||
db: Session = Depends(get_db),
|
db: Session = Depends(get_db),
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
@@ -1100,7 +1085,7 @@ async def admin_billing_history_page(
|
|||||||
@router.get("/settings", response_class=HTMLResponse, include_in_schema=False)
|
@router.get("/settings", response_class=HTMLResponse, include_in_schema=False)
|
||||||
async def admin_settings_page(
|
async def admin_settings_page(
|
||||||
request: Request,
|
request: Request,
|
||||||
current_user: User = Depends(get_current_admin_from_cookie_or_header),
|
current_user: User = Depends(require_menu_access("settings", FrontendType.ADMIN)),
|
||||||
db: Session = Depends(get_db),
|
db: Session = Depends(get_db),
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
@@ -1116,10 +1101,33 @@ async def admin_settings_page(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/my-menu", response_class=HTMLResponse, include_in_schema=False)
|
||||||
|
async def admin_my_menu_config(
|
||||||
|
request: Request,
|
||||||
|
current_user: User = Depends(require_menu_access("my-menu", FrontendType.ADMIN)),
|
||||||
|
db: Session = Depends(get_db),
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Render personal menu configuration page for super admins.
|
||||||
|
Allows super admins to customize their own sidebar menu.
|
||||||
|
"""
|
||||||
|
# Only super admins can configure their own menu
|
||||||
|
if not current_user.is_super_admin:
|
||||||
|
return RedirectResponse(url="/admin/settings", status_code=302)
|
||||||
|
|
||||||
|
return templates.TemplateResponse(
|
||||||
|
"admin/my-menu-config.html",
|
||||||
|
{
|
||||||
|
"request": request,
|
||||||
|
"user": current_user,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@router.get("/logs", response_class=HTMLResponse, include_in_schema=False)
|
@router.get("/logs", response_class=HTMLResponse, include_in_schema=False)
|
||||||
async def admin_logs_page(
|
async def admin_logs_page(
|
||||||
request: Request,
|
request: Request,
|
||||||
current_user: User = Depends(get_current_admin_from_cookie_or_header),
|
current_user: User = Depends(require_menu_access("logs", FrontendType.ADMIN)),
|
||||||
db: Session = Depends(get_db),
|
db: Session = Depends(get_db),
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
@@ -1143,7 +1151,7 @@ async def admin_logs_page(
|
|||||||
@router.get("/platforms", response_class=HTMLResponse, include_in_schema=False)
|
@router.get("/platforms", response_class=HTMLResponse, include_in_schema=False)
|
||||||
async def admin_platforms_list(
|
async def admin_platforms_list(
|
||||||
request: Request,
|
request: Request,
|
||||||
current_user: User = Depends(get_current_admin_from_cookie_or_header),
|
current_user: User = Depends(require_menu_access("platforms", FrontendType.ADMIN)),
|
||||||
db: Session = Depends(get_db),
|
db: Session = Depends(get_db),
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
@@ -1165,7 +1173,7 @@ async def admin_platforms_list(
|
|||||||
async def admin_platform_detail(
|
async def admin_platform_detail(
|
||||||
request: Request,
|
request: Request,
|
||||||
platform_code: str = Path(..., description="Platform code (oms, loyalty, etc.)"),
|
platform_code: str = Path(..., description="Platform code (oms, loyalty, etc.)"),
|
||||||
current_user: User = Depends(get_current_admin_from_cookie_or_header),
|
current_user: User = Depends(require_menu_access("platforms", FrontendType.ADMIN)),
|
||||||
db: Session = Depends(get_db),
|
db: Session = Depends(get_db),
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
@@ -1190,7 +1198,7 @@ async def admin_platform_detail(
|
|||||||
async def admin_platform_edit(
|
async def admin_platform_edit(
|
||||||
request: Request,
|
request: Request,
|
||||||
platform_code: str = Path(..., description="Platform code"),
|
platform_code: str = Path(..., description="Platform code"),
|
||||||
current_user: User = Depends(get_current_admin_from_cookie_or_header),
|
current_user: User = Depends(require_menu_access("platforms", FrontendType.ADMIN)),
|
||||||
db: Session = Depends(get_db),
|
db: Session = Depends(get_db),
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
@@ -1207,6 +1215,66 @@ async def admin_platform_edit(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@router.get(
|
||||||
|
"/platforms/{platform_code}/menu-config",
|
||||||
|
response_class=HTMLResponse,
|
||||||
|
include_in_schema=False,
|
||||||
|
)
|
||||||
|
async def admin_platform_menu_config(
|
||||||
|
request: Request,
|
||||||
|
platform_code: str = Path(..., description="Platform code"),
|
||||||
|
current_user: User = Depends(require_menu_access("platforms", FrontendType.ADMIN)),
|
||||||
|
db: Session = Depends(get_db),
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Render platform menu configuration page.
|
||||||
|
Super admin only - allows configuring which menu items are visible
|
||||||
|
for the platform's admin and vendor frontends.
|
||||||
|
"""
|
||||||
|
# Only super admins can access menu configuration
|
||||||
|
if not current_user.is_super_admin:
|
||||||
|
return RedirectResponse(url=f"/admin/platforms/{platform_code}", status_code=302)
|
||||||
|
|
||||||
|
return templates.TemplateResponse(
|
||||||
|
"admin/platform-menu-config.html",
|
||||||
|
{
|
||||||
|
"request": request,
|
||||||
|
"user": current_user,
|
||||||
|
"platform_code": platform_code,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@router.get(
|
||||||
|
"/platforms/{platform_code}/modules",
|
||||||
|
response_class=HTMLResponse,
|
||||||
|
include_in_schema=False,
|
||||||
|
)
|
||||||
|
async def admin_platform_modules(
|
||||||
|
request: Request,
|
||||||
|
platform_code: str = Path(..., description="Platform code"),
|
||||||
|
current_user: User = Depends(require_menu_access("platforms", FrontendType.ADMIN)),
|
||||||
|
db: Session = Depends(get_db),
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Render platform module configuration page.
|
||||||
|
Super admin only - allows enabling/disabling feature modules
|
||||||
|
for the platform.
|
||||||
|
"""
|
||||||
|
# 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/platform-modules.html",
|
||||||
|
{
|
||||||
|
"request": request,
|
||||||
|
"user": current_user,
|
||||||
|
"platform_code": platform_code,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
# CONTENT MANAGEMENT SYSTEM (CMS) ROUTES
|
# CONTENT MANAGEMENT SYSTEM (CMS) ROUTES
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
@@ -1215,7 +1283,7 @@ async def admin_platform_edit(
|
|||||||
@router.get("/platform-homepage", include_in_schema=False)
|
@router.get("/platform-homepage", include_in_schema=False)
|
||||||
async def admin_platform_homepage_manager(
|
async def admin_platform_homepage_manager(
|
||||||
request: Request,
|
request: Request,
|
||||||
current_user: User = Depends(get_current_admin_from_cookie_or_header),
|
current_user: User = Depends(require_menu_access("platforms", FrontendType.ADMIN)),
|
||||||
db: Session = Depends(get_db),
|
db: Session = Depends(get_db),
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
@@ -1225,14 +1293,13 @@ async def admin_platform_homepage_manager(
|
|||||||
- /admin/platforms → Select platform → Homepage button
|
- /admin/platforms → Select platform → Homepage button
|
||||||
- Or directly: /admin/content-pages?platform_code={code}&slug=home
|
- Or directly: /admin/content-pages?platform_code={code}&slug=home
|
||||||
"""
|
"""
|
||||||
from starlette.responses import RedirectResponse
|
|
||||||
return RedirectResponse(url="/admin/platforms", status_code=302)
|
return RedirectResponse(url="/admin/platforms", status_code=302)
|
||||||
|
|
||||||
|
|
||||||
@router.get("/content-pages", response_class=HTMLResponse, include_in_schema=False)
|
@router.get("/content-pages", response_class=HTMLResponse, include_in_schema=False)
|
||||||
async def admin_content_pages_list(
|
async def admin_content_pages_list(
|
||||||
request: Request,
|
request: Request,
|
||||||
current_user: User = Depends(get_current_admin_from_cookie_or_header),
|
current_user: User = Depends(require_menu_access("content-pages", FrontendType.ADMIN)),
|
||||||
db: Session = Depends(get_db),
|
db: Session = Depends(get_db),
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
@@ -1253,7 +1320,7 @@ async def admin_content_pages_list(
|
|||||||
)
|
)
|
||||||
async def admin_content_page_create(
|
async def admin_content_page_create(
|
||||||
request: Request,
|
request: Request,
|
||||||
current_user: User = Depends(get_current_admin_from_cookie_or_header),
|
current_user: User = Depends(require_menu_access("content-pages", FrontendType.ADMIN)),
|
||||||
db: Session = Depends(get_db),
|
db: Session = Depends(get_db),
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
@@ -1278,7 +1345,7 @@ async def admin_content_page_create(
|
|||||||
async def admin_content_page_edit(
|
async def admin_content_page_edit(
|
||||||
request: Request,
|
request: Request,
|
||||||
page_id: int = Path(..., description="Content page ID"),
|
page_id: int = Path(..., description="Content page ID"),
|
||||||
current_user: User = Depends(get_current_admin_from_cookie_or_header),
|
current_user: User = Depends(require_menu_access("content-pages", FrontendType.ADMIN)),
|
||||||
db: Session = Depends(get_db),
|
db: Session = Depends(get_db),
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
@@ -1303,7 +1370,7 @@ async def admin_content_page_edit(
|
|||||||
@router.get("/components", response_class=HTMLResponse, include_in_schema=False)
|
@router.get("/components", response_class=HTMLResponse, include_in_schema=False)
|
||||||
async def admin_components_page(
|
async def admin_components_page(
|
||||||
request: Request,
|
request: Request,
|
||||||
current_user: User = Depends(get_current_admin_from_cookie_or_header),
|
current_user: User = Depends(require_menu_access("components", FrontendType.ADMIN)),
|
||||||
db: Session = Depends(get_db),
|
db: Session = Depends(get_db),
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
@@ -1322,7 +1389,7 @@ async def admin_components_page(
|
|||||||
@router.get("/icons", response_class=HTMLResponse, include_in_schema=False)
|
@router.get("/icons", response_class=HTMLResponse, include_in_schema=False)
|
||||||
async def admin_icons_page(
|
async def admin_icons_page(
|
||||||
request: Request,
|
request: Request,
|
||||||
current_user: User = Depends(get_current_admin_from_cookie_or_header),
|
current_user: User = Depends(require_menu_access("icons", FrontendType.ADMIN)),
|
||||||
db: Session = Depends(get_db),
|
db: Session = Depends(get_db),
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
@@ -1341,7 +1408,7 @@ async def admin_icons_page(
|
|||||||
@router.get("/testing", response_class=HTMLResponse, include_in_schema=False)
|
@router.get("/testing", response_class=HTMLResponse, include_in_schema=False)
|
||||||
async def admin_testing_dashboard(
|
async def admin_testing_dashboard(
|
||||||
request: Request,
|
request: Request,
|
||||||
current_user: User = Depends(get_current_admin_from_cookie_or_header),
|
current_user: User = Depends(require_menu_access("testing", FrontendType.ADMIN)),
|
||||||
db: Session = Depends(get_db),
|
db: Session = Depends(get_db),
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
@@ -1360,7 +1427,7 @@ async def admin_testing_dashboard(
|
|||||||
@router.get("/testing-hub", response_class=HTMLResponse, include_in_schema=False)
|
@router.get("/testing-hub", response_class=HTMLResponse, include_in_schema=False)
|
||||||
async def admin_testing_hub(
|
async def admin_testing_hub(
|
||||||
request: Request,
|
request: Request,
|
||||||
current_user: User = Depends(get_current_admin_from_cookie_or_header),
|
current_user: User = Depends(require_menu_access("testing", FrontendType.ADMIN)),
|
||||||
db: Session = Depends(get_db),
|
db: Session = Depends(get_db),
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
@@ -1379,7 +1446,7 @@ async def admin_testing_hub(
|
|||||||
@router.get("/test/auth-flow", response_class=HTMLResponse, include_in_schema=False)
|
@router.get("/test/auth-flow", response_class=HTMLResponse, include_in_schema=False)
|
||||||
async def admin_test_auth_flow(
|
async def admin_test_auth_flow(
|
||||||
request: Request,
|
request: Request,
|
||||||
current_user: User = Depends(get_current_admin_from_cookie_or_header),
|
current_user: User = Depends(require_menu_access("testing", FrontendType.ADMIN)),
|
||||||
db: Session = Depends(get_db),
|
db: Session = Depends(get_db),
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
@@ -1402,7 +1469,7 @@ async def admin_test_auth_flow(
|
|||||||
)
|
)
|
||||||
async def admin_test_vendors_users_migration(
|
async def admin_test_vendors_users_migration(
|
||||||
request: Request,
|
request: Request,
|
||||||
current_user: User = Depends(get_current_admin_from_cookie_or_header),
|
current_user: User = Depends(require_menu_access("testing", FrontendType.ADMIN)),
|
||||||
db: Session = Depends(get_db),
|
db: Session = Depends(get_db),
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
@@ -1426,7 +1493,7 @@ async def admin_test_vendors_users_migration(
|
|||||||
@router.get("/code-quality", response_class=HTMLResponse, include_in_schema=False)
|
@router.get("/code-quality", response_class=HTMLResponse, include_in_schema=False)
|
||||||
async def admin_code_quality_dashboard(
|
async def admin_code_quality_dashboard(
|
||||||
request: Request,
|
request: Request,
|
||||||
current_user: User = Depends(get_current_admin_from_cookie_or_header),
|
current_user: User = Depends(require_menu_access("code-quality", FrontendType.ADMIN)),
|
||||||
db: Session = Depends(get_db),
|
db: Session = Depends(get_db),
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
@@ -1447,7 +1514,7 @@ async def admin_code_quality_dashboard(
|
|||||||
)
|
)
|
||||||
async def admin_code_quality_violations(
|
async def admin_code_quality_violations(
|
||||||
request: Request,
|
request: Request,
|
||||||
current_user: User = Depends(get_current_admin_from_cookie_or_header),
|
current_user: User = Depends(require_menu_access("code-quality", FrontendType.ADMIN)),
|
||||||
db: Session = Depends(get_db),
|
db: Session = Depends(get_db),
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
@@ -1471,7 +1538,7 @@ async def admin_code_quality_violations(
|
|||||||
async def admin_code_quality_violation_detail(
|
async def admin_code_quality_violation_detail(
|
||||||
request: Request,
|
request: Request,
|
||||||
violation_id: int = Path(..., description="Violation ID"),
|
violation_id: int = Path(..., description="Violation ID"),
|
||||||
current_user: User = Depends(get_current_admin_from_cookie_or_header),
|
current_user: User = Depends(require_menu_access("code-quality", FrontendType.ADMIN)),
|
||||||
db: Session = Depends(get_db),
|
db: Session = Depends(get_db),
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
@@ -1496,7 +1563,7 @@ async def admin_code_quality_violation_detail(
|
|||||||
@router.get("/platform-health", response_class=HTMLResponse, include_in_schema=False)
|
@router.get("/platform-health", response_class=HTMLResponse, include_in_schema=False)
|
||||||
async def admin_platform_health(
|
async def admin_platform_health(
|
||||||
request: Request,
|
request: Request,
|
||||||
current_user: User = Depends(get_current_admin_from_cookie_or_header),
|
current_user: User = Depends(require_menu_access("platform-health", FrontendType.ADMIN)),
|
||||||
db: Session = Depends(get_db),
|
db: Session = Depends(get_db),
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
@@ -1520,7 +1587,7 @@ async def admin_platform_health(
|
|||||||
@router.get("/features", response_class=HTMLResponse, include_in_schema=False)
|
@router.get("/features", response_class=HTMLResponse, include_in_schema=False)
|
||||||
async def admin_features_page(
|
async def admin_features_page(
|
||||||
request: Request,
|
request: Request,
|
||||||
current_user: User = Depends(get_current_admin_from_cookie_or_header),
|
current_user: User = Depends(require_menu_access("subscription-tiers", FrontendType.ADMIN)),
|
||||||
db: Session = Depends(get_db),
|
db: Session = Depends(get_db),
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -90,6 +90,37 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Super Admin Actions (Menu Configuration) -->
|
||||||
|
<div x-show="isSuperAdmin" class="bg-white dark:bg-gray-800 rounded-lg shadow-md p-6">
|
||||||
|
<h3 class="text-lg font-semibold text-gray-900 dark:text-white mb-4">
|
||||||
|
<span class="inline-flex items-center">
|
||||||
|
Super Admin
|
||||||
|
<span class="ml-2 px-2 py-0.5 text-xs font-medium rounded bg-purple-100 text-purple-800 dark:bg-purple-900 dark:text-purple-200">Admin Only</span>
|
||||||
|
</span>
|
||||||
|
</h3>
|
||||||
|
<div class="grid grid-cols-1 md:grid-cols-4 gap-4">
|
||||||
|
<!-- Module Configuration -->
|
||||||
|
<a :href="`/admin/platforms/${platformCode}/modules`"
|
||||||
|
class="flex items-center p-4 bg-green-50 dark:bg-green-900/20 rounded-lg hover:bg-green-100 dark:hover:bg-green-900/40 transition-colors">
|
||||||
|
<span x-html="$icon('puzzle', 'w-8 h-8 text-green-600 dark:text-green-400')"></span>
|
||||||
|
<div class="ml-3">
|
||||||
|
<p class="font-semibold text-gray-900 dark:text-white">Module Configuration</p>
|
||||||
|
<p class="text-sm text-gray-600 dark:text-gray-400">Enable/disable features</p>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<!-- Menu Configuration -->
|
||||||
|
<a :href="`/admin/platforms/${platformCode}/menu-config`"
|
||||||
|
class="flex items-center p-4 bg-amber-50 dark:bg-amber-900/20 rounded-lg hover:bg-amber-100 dark:hover:bg-amber-900/40 transition-colors">
|
||||||
|
<span x-html="$icon('view-grid', 'w-8 h-8 text-amber-600 dark:text-amber-400')"></span>
|
||||||
|
<div class="ml-3">
|
||||||
|
<p class="font-semibold text-gray-900 dark:text-white">Menu Configuration</p>
|
||||||
|
<p class="text-sm text-gray-600 dark:text-gray-400">Admin & vendor menus</p>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Stats Grid -->
|
<!-- Stats Grid -->
|
||||||
<div class="grid grid-cols-1 md:grid-cols-4 gap-6">
|
<div class="grid grid-cols-1 md:grid-cols-4 gap-6">
|
||||||
<!-- Vendors -->
|
<!-- Vendors -->
|
||||||
|
|||||||
252
app/templates/admin/platform-modules.html
Normal file
252
app/templates/admin/platform-modules.html
Normal file
@@ -0,0 +1,252 @@
|
|||||||
|
{# app/templates/admin/platform-modules.html #}
|
||||||
|
{% extends "admin/base.html" %}
|
||||||
|
{% from 'shared/macros/alerts.html' import alert_dynamic, error_state %}
|
||||||
|
{% from 'shared/macros/headers.html' import page_header %}
|
||||||
|
|
||||||
|
{% block title %}Module Configuration{% endblock %}
|
||||||
|
|
||||||
|
{% block alpine_data %}adminPlatformModules('{{ platform_code }}'){% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
{{ page_header('Module Configuration', back_url='/admin/platforms/' + platform_code) }}
|
||||||
|
|
||||||
|
{{ alert_dynamic(type='success', title='Success', message_var='successMessage', show_condition='successMessage') }}
|
||||||
|
{{ error_state('Error', show_condition='error') }}
|
||||||
|
|
||||||
|
<!-- Platform Info -->
|
||||||
|
<div class="mb-6 p-4 bg-white rounded-lg shadow-xs dark:bg-gray-800">
|
||||||
|
<div class="flex items-center justify-between">
|
||||||
|
<div>
|
||||||
|
<h2 class="text-lg font-semibold text-gray-700 dark:text-gray-200" x-text="platform?.name || 'Loading...'"></h2>
|
||||||
|
<p class="text-sm text-gray-500 dark:text-gray-400">
|
||||||
|
Enable or disable feature modules for this platform. Core modules cannot be disabled.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<span class="px-3 py-1 text-sm font-medium rounded-full bg-purple-100 text-purple-800 dark:bg-purple-900 dark:text-purple-200" x-text="platform?.code?.toUpperCase()"></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Stats Cards -->
|
||||||
|
<div class="grid gap-4 mb-6 md:grid-cols-4">
|
||||||
|
<div class="flex items-center p-4 bg-white rounded-lg shadow-xs dark:bg-gray-800">
|
||||||
|
<div class="p-3 mr-4 text-blue-500 bg-blue-100 rounded-full dark:text-blue-100 dark:bg-blue-500">
|
||||||
|
<span x-html="$icon('puzzle', 'w-5 h-5')"></span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<p class="mb-1 text-sm font-medium text-gray-600 dark:text-gray-400">Total Modules</p>
|
||||||
|
<p class="text-lg font-semibold text-gray-700 dark:text-gray-200" x-text="moduleConfig?.total || 0"></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex items-center p-4 bg-white rounded-lg shadow-xs dark:bg-gray-800">
|
||||||
|
<div class="p-3 mr-4 text-green-500 bg-green-100 rounded-full dark:text-green-100 dark:bg-green-500">
|
||||||
|
<span x-html="$icon('check-circle', 'w-5 h-5')"></span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<p class="mb-1 text-sm font-medium text-gray-600 dark:text-gray-400">Enabled</p>
|
||||||
|
<p class="text-lg font-semibold text-gray-700 dark:text-gray-200" x-text="moduleConfig?.enabled || 0"></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex items-center p-4 bg-white rounded-lg shadow-xs dark:bg-gray-800">
|
||||||
|
<div class="p-3 mr-4 text-gray-500 bg-gray-100 rounded-full dark:text-gray-100 dark:bg-gray-600">
|
||||||
|
<span x-html="$icon('x-circle', 'w-5 h-5')"></span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<p class="mb-1 text-sm font-medium text-gray-600 dark:text-gray-400">Disabled</p>
|
||||||
|
<p class="text-lg font-semibold text-gray-700 dark:text-gray-200" x-text="moduleConfig?.disabled || 0"></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex items-center p-4 bg-white rounded-lg shadow-xs dark:bg-gray-800">
|
||||||
|
<div class="p-3 mr-4 text-purple-500 bg-purple-100 rounded-full dark:text-purple-100 dark:bg-purple-500">
|
||||||
|
<span x-html="$icon('shield', 'w-5 h-5')"></span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<p class="mb-1 text-sm font-medium text-gray-600 dark:text-gray-400">Core Modules</p>
|
||||||
|
<p class="text-lg font-semibold text-gray-700 dark:text-gray-200" x-text="coreModulesCount"></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Actions -->
|
||||||
|
<div class="mb-4 flex items-center justify-between">
|
||||||
|
<p class="text-sm text-gray-500 dark:text-gray-400">
|
||||||
|
Toggle modules on/off. Dependencies are resolved automatically.
|
||||||
|
</p>
|
||||||
|
<div class="flex gap-2">
|
||||||
|
<button
|
||||||
|
@click="enableAll()"
|
||||||
|
:disabled="saving"
|
||||||
|
class="inline-flex items-center px-4 py-2 text-sm font-medium text-gray-700 bg-white border border-gray-300 rounded-lg hover:bg-gray-50 dark:bg-gray-700 dark:text-gray-300 dark:border-gray-600 dark:hover:bg-gray-600"
|
||||||
|
>
|
||||||
|
<span x-html="$icon('check-circle', 'w-4 h-4 mr-2')"></span>
|
||||||
|
Enable All
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
@click="disableOptional()"
|
||||||
|
:disabled="saving"
|
||||||
|
class="inline-flex items-center px-4 py-2 text-sm font-medium text-gray-700 bg-white border border-gray-300 rounded-lg hover:bg-gray-50 dark:bg-gray-700 dark:text-gray-300 dark:border-gray-600 dark:hover:bg-gray-600"
|
||||||
|
>
|
||||||
|
<span x-html="$icon('x-circle', 'w-4 h-4 mr-2')"></span>
|
||||||
|
Core Only
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Loading State -->
|
||||||
|
<div x-show="loading" class="flex items-center justify-center py-12 bg-white rounded-lg shadow-xs dark:bg-gray-800">
|
||||||
|
<span x-html="$icon('refresh', 'w-8 h-8 animate-spin text-purple-600')"></span>
|
||||||
|
<span class="ml-3 text-gray-500 dark:text-gray-400">Loading module configuration...</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Module Groups -->
|
||||||
|
<div x-show="!loading" class="space-y-6">
|
||||||
|
<!-- Core Modules -->
|
||||||
|
<div class="bg-white rounded-lg shadow-xs dark:bg-gray-800 overflow-hidden">
|
||||||
|
<div class="px-4 py-3 bg-purple-50 dark:bg-purple-900/20 border-b border-purple-200 dark:border-purple-800">
|
||||||
|
<div class="flex items-center justify-between">
|
||||||
|
<div class="flex items-center">
|
||||||
|
<span x-html="$icon('shield', 'w-5 h-5 text-purple-600 dark:text-purple-400 mr-2')"></span>
|
||||||
|
<h3 class="text-sm font-semibold text-purple-800 dark:text-purple-200">Core Modules</h3>
|
||||||
|
<span class="ml-2 px-2 py-0.5 text-xs font-medium rounded bg-purple-100 text-purple-800 dark:bg-purple-900 dark:text-purple-200">
|
||||||
|
Always Enabled
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<span class="text-xs text-purple-600 dark:text-purple-400" x-text="`${coreModulesCount} modules`"></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="divide-y divide-gray-100 dark:divide-gray-700">
|
||||||
|
<template x-for="module in coreModules" :key="module.code">
|
||||||
|
<div class="flex items-center justify-between px-4 py-4 hover:bg-gray-50 dark:hover:bg-gray-700/30">
|
||||||
|
<div class="flex items-center flex-1">
|
||||||
|
<div class="p-2 mr-3 bg-purple-100 dark:bg-purple-900/30 rounded-lg">
|
||||||
|
<span x-html="$icon(getModuleIcon(module.code), 'w-5 h-5 text-purple-600 dark:text-purple-400')"></span>
|
||||||
|
</div>
|
||||||
|
<div class="flex-1">
|
||||||
|
<p class="text-sm font-medium text-gray-700 dark:text-gray-200" x-text="module.name"></p>
|
||||||
|
<p class="text-xs text-gray-500 dark:text-gray-400" x-text="module.description"></p>
|
||||||
|
<!-- Features -->
|
||||||
|
<div x-show="module.features?.length > 0" class="mt-1 flex flex-wrap gap-1">
|
||||||
|
<template x-for="feature in module.features.slice(0, 3)" :key="feature">
|
||||||
|
<span class="px-1.5 py-0.5 text-xs rounded bg-gray-100 text-gray-600 dark:bg-gray-700 dark:text-gray-400" x-text="feature"></span>
|
||||||
|
</template>
|
||||||
|
<span x-show="module.features?.length > 3" class="text-xs text-gray-400" x-text="`+${module.features.length - 3} more`"></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center gap-3 ml-4">
|
||||||
|
<span class="px-2 py-1 text-xs font-medium rounded-full bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200">
|
||||||
|
Enabled
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Optional Modules -->
|
||||||
|
<div class="bg-white rounded-lg shadow-xs dark:bg-gray-800 overflow-hidden">
|
||||||
|
<div class="px-4 py-3 bg-gray-50 dark:bg-gray-700/50 border-b border-gray-200 dark:border-gray-700">
|
||||||
|
<div class="flex items-center justify-between">
|
||||||
|
<div class="flex items-center">
|
||||||
|
<span x-html="$icon('puzzle', 'w-5 h-5 text-gray-600 dark:text-gray-400 mr-2')"></span>
|
||||||
|
<h3 class="text-sm font-semibold text-gray-700 dark:text-gray-200">Optional Modules</h3>
|
||||||
|
</div>
|
||||||
|
<span class="text-xs text-gray-500 dark:text-gray-400" x-text="`${enabledOptionalCount}/${optionalModules.length} enabled`"></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="divide-y divide-gray-100 dark:divide-gray-700">
|
||||||
|
<template x-for="module in optionalModules" :key="module.code">
|
||||||
|
<div class="flex items-center justify-between px-4 py-4 hover:bg-gray-50 dark:hover:bg-gray-700/30">
|
||||||
|
<div class="flex items-center flex-1">
|
||||||
|
<div class="p-2 mr-3 rounded-lg"
|
||||||
|
:class="module.is_enabled ? 'bg-green-100 dark:bg-green-900/30' : 'bg-gray-100 dark:bg-gray-700'">
|
||||||
|
<span x-html="$icon(getModuleIcon(module.code), 'w-5 h-5')"
|
||||||
|
:class="module.is_enabled ? 'text-green-600 dark:text-green-400' : 'text-gray-400'"></span>
|
||||||
|
</div>
|
||||||
|
<div class="flex-1">
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<p class="text-sm font-medium text-gray-700 dark:text-gray-200" x-text="module.name"></p>
|
||||||
|
<!-- Dependencies Badge -->
|
||||||
|
<template x-if="module.requires?.length > 0">
|
||||||
|
<span class="px-1.5 py-0.5 text-xs rounded bg-amber-100 text-amber-700 dark:bg-amber-900/30 dark:text-amber-400"
|
||||||
|
:title="`Requires: ${module.requires.join(', ')}`">
|
||||||
|
<span x-html="$icon('link', 'w-3 h-3 inline')"></span>
|
||||||
|
<span x-text="module.requires.length"></span>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
<!-- Dependents Badge -->
|
||||||
|
<template x-if="module.dependent_modules?.length > 0">
|
||||||
|
<span class="px-1.5 py-0.5 text-xs rounded bg-blue-100 text-blue-700 dark:bg-blue-900/30 dark:text-blue-400"
|
||||||
|
:title="`Required by: ${module.dependent_modules.join(', ')}`">
|
||||||
|
<span x-html="$icon('users', 'w-3 h-3 inline')"></span>
|
||||||
|
<span x-text="module.dependent_modules.length"></span>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
<p class="text-xs text-gray-500 dark:text-gray-400" x-text="module.description"></p>
|
||||||
|
<!-- Dependencies Info -->
|
||||||
|
<div x-show="module.requires?.length > 0" class="mt-1">
|
||||||
|
<span class="text-xs text-amber-600 dark:text-amber-400">
|
||||||
|
Requires: <span x-text="module.requires.join(', ')"></span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<!-- Features -->
|
||||||
|
<div x-show="module.features?.length > 0" class="mt-1 flex flex-wrap gap-1">
|
||||||
|
<template x-for="feature in module.features.slice(0, 3)" :key="feature">
|
||||||
|
<span class="px-1.5 py-0.5 text-xs rounded bg-gray-100 text-gray-600 dark:bg-gray-700 dark:text-gray-400" x-text="feature"></span>
|
||||||
|
</template>
|
||||||
|
<span x-show="module.features?.length > 3" class="text-xs text-gray-400" x-text="`+${module.features.length - 3} more`"></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center gap-3 ml-4">
|
||||||
|
<!-- Status Badge -->
|
||||||
|
<span x-show="module.is_enabled"
|
||||||
|
class="px-2 py-1 text-xs font-medium rounded-full bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200">
|
||||||
|
Enabled
|
||||||
|
</span>
|
||||||
|
<span x-show="!module.is_enabled"
|
||||||
|
class="px-2 py-1 text-xs font-medium rounded-full bg-gray-100 text-gray-600 dark:bg-gray-700 dark:text-gray-400">
|
||||||
|
Disabled
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<!-- Toggle Switch -->
|
||||||
|
<button
|
||||||
|
@click="toggleModule(module)"
|
||||||
|
:disabled="saving"
|
||||||
|
:class="{
|
||||||
|
'bg-purple-600': module.is_enabled,
|
||||||
|
'bg-gray-200 dark:bg-gray-600': !module.is_enabled
|
||||||
|
}"
|
||||||
|
class="relative inline-flex h-6 w-11 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none focus:ring-2 focus:ring-purple-500 focus:ring-offset-2"
|
||||||
|
role="switch"
|
||||||
|
:aria-checked="module.is_enabled"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
:class="{
|
||||||
|
'translate-x-5': module.is_enabled,
|
||||||
|
'translate-x-0': !module.is_enabled
|
||||||
|
}"
|
||||||
|
class="pointer-events-none inline-block h-5 w-5 transform rounded-full bg-white shadow ring-0 transition duration-200 ease-in-out"
|
||||||
|
></span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Empty State -->
|
||||||
|
<div x-show="moduleConfig?.modules?.length === 0" class="bg-white rounded-lg shadow-xs dark:bg-gray-800 p-8 text-center">
|
||||||
|
<span x-html="$icon('puzzle', 'w-12 h-12 mx-auto text-gray-400')"></span>
|
||||||
|
<p class="mt-4 text-gray-500 dark:text-gray-400">No modules available.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block extra_scripts %}
|
||||||
|
<script src="{{ url_for('static', path='admin/js/platform-modules.js') }}"></script>
|
||||||
|
{% endblock %}
|
||||||
227
static/admin/js/platform-modules.js
Normal file
227
static/admin/js/platform-modules.js
Normal file
@@ -0,0 +1,227 @@
|
|||||||
|
// static/admin/js/platform-modules.js
|
||||||
|
// Platform module configuration management
|
||||||
|
|
||||||
|
const moduleConfigLog = window.LogConfig?.loggers?.moduleConfig || window.LogConfig?.createLogger?.('moduleConfig') || console;
|
||||||
|
|
||||||
|
function adminPlatformModules(platformCode) {
|
||||||
|
// Get base data with safety check for standalone usage
|
||||||
|
const baseData = typeof data === 'function' ? data() : {};
|
||||||
|
|
||||||
|
return {
|
||||||
|
// Inherit base layout functionality from init-alpine.js
|
||||||
|
...baseData,
|
||||||
|
|
||||||
|
// Page-specific state
|
||||||
|
currentPage: 'platforms',
|
||||||
|
platformCode: platformCode,
|
||||||
|
loading: true,
|
||||||
|
error: null,
|
||||||
|
successMessage: null,
|
||||||
|
saving: false,
|
||||||
|
|
||||||
|
// Data
|
||||||
|
platform: null,
|
||||||
|
moduleConfig: null,
|
||||||
|
|
||||||
|
// Computed properties
|
||||||
|
get coreModules() {
|
||||||
|
if (!this.moduleConfig?.modules) return [];
|
||||||
|
return this.moduleConfig.modules.filter(m => m.is_core);
|
||||||
|
},
|
||||||
|
|
||||||
|
get optionalModules() {
|
||||||
|
if (!this.moduleConfig?.modules) return [];
|
||||||
|
return this.moduleConfig.modules.filter(m => !m.is_core);
|
||||||
|
},
|
||||||
|
|
||||||
|
get coreModulesCount() {
|
||||||
|
return this.coreModules.length;
|
||||||
|
},
|
||||||
|
|
||||||
|
get enabledOptionalCount() {
|
||||||
|
return this.optionalModules.filter(m => m.is_enabled).length;
|
||||||
|
},
|
||||||
|
|
||||||
|
// Module icons mapping
|
||||||
|
getModuleIcon(moduleCode) {
|
||||||
|
const icons = {
|
||||||
|
'core': 'home',
|
||||||
|
'platform-admin': 'building-office',
|
||||||
|
'billing': 'credit-card',
|
||||||
|
'inventory': 'archive-box',
|
||||||
|
'orders': 'shopping-cart',
|
||||||
|
'marketplace': 'shopping-bag',
|
||||||
|
'customers': 'users',
|
||||||
|
'cms': 'document-text',
|
||||||
|
'analytics': 'chart-bar',
|
||||||
|
'messaging': 'chat-bubble-left-right',
|
||||||
|
'dev-tools': 'code-bracket',
|
||||||
|
'monitoring': 'chart-bar-square'
|
||||||
|
};
|
||||||
|
return icons[moduleCode] || 'puzzle';
|
||||||
|
},
|
||||||
|
|
||||||
|
async init() {
|
||||||
|
moduleConfigLog.info('=== PLATFORM MODULES PAGE INITIALIZING ===');
|
||||||
|
moduleConfigLog.info('Platform code:', this.platformCode);
|
||||||
|
|
||||||
|
try {
|
||||||
|
await this.loadPlatform();
|
||||||
|
await this.loadModuleConfig();
|
||||||
|
moduleConfigLog.info('=== PLATFORM MODULES PAGE INITIALIZED ===');
|
||||||
|
} catch (error) {
|
||||||
|
moduleConfigLog.error('Failed to initialize modules page:', error);
|
||||||
|
this.error = 'Failed to load page data. Please refresh.';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async refresh() {
|
||||||
|
this.error = null;
|
||||||
|
this.successMessage = null;
|
||||||
|
await this.loadModuleConfig();
|
||||||
|
},
|
||||||
|
|
||||||
|
async loadPlatform() {
|
||||||
|
try {
|
||||||
|
this.platform = await apiClient.get(`/admin/platforms/${this.platformCode}`);
|
||||||
|
moduleConfigLog.info('Loaded platform:', this.platform?.name);
|
||||||
|
} catch (error) {
|
||||||
|
moduleConfigLog.error('Failed to load platform:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async loadModuleConfig() {
|
||||||
|
this.loading = true;
|
||||||
|
this.error = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const platformId = this.platform?.id;
|
||||||
|
if (!platformId) {
|
||||||
|
throw new Error('Platform not loaded');
|
||||||
|
}
|
||||||
|
|
||||||
|
this.moduleConfig = await apiClient.get(`/admin/modules/platforms/${platformId}`);
|
||||||
|
moduleConfigLog.info('Loaded module config:', {
|
||||||
|
total: this.moduleConfig?.total,
|
||||||
|
enabled: this.moduleConfig?.enabled,
|
||||||
|
disabled: this.moduleConfig?.disabled
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
moduleConfigLog.error('Failed to load module config:', error);
|
||||||
|
this.error = error.message || 'Failed to load module configuration';
|
||||||
|
} finally {
|
||||||
|
this.loading = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async toggleModule(module) {
|
||||||
|
if (module.is_core) {
|
||||||
|
moduleConfigLog.warn('Cannot toggle core module:', module.code);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.saving = true;
|
||||||
|
this.error = null;
|
||||||
|
this.successMessage = null;
|
||||||
|
|
||||||
|
const action = 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: module.code
|
||||||
|
});
|
||||||
|
|
||||||
|
moduleConfigLog.info(`${action}d module:`, module.code, result);
|
||||||
|
|
||||||
|
// Show success message
|
||||||
|
if (result.also_enabled?.length > 0) {
|
||||||
|
this.successMessage = `Module '${module.name}' enabled. Also enabled dependencies: ${result.also_enabled.join(', ')}`;
|
||||||
|
} else if (result.also_disabled?.length > 0) {
|
||||||
|
this.successMessage = `Module '${module.name}' disabled. Also disabled dependents: ${result.also_disabled.join(', ')}`;
|
||||||
|
} else {
|
||||||
|
this.successMessage = `Module '${module.name}' ${action}d successfully`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reload module config to get updated state
|
||||||
|
await this.loadModuleConfig();
|
||||||
|
|
||||||
|
// Clear success message after delay
|
||||||
|
setTimeout(() => {
|
||||||
|
this.successMessage = null;
|
||||||
|
}, 5000);
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
moduleConfigLog.error(`Failed to ${action} module:`, error);
|
||||||
|
this.error = error.message || `Failed to ${action} module`;
|
||||||
|
} finally {
|
||||||
|
this.saving = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async enableAll() {
|
||||||
|
if (!confirm('This will enable all modules. Continue?')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.saving = true;
|
||||||
|
this.error = null;
|
||||||
|
this.successMessage = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const platformId = this.platform?.id;
|
||||||
|
const result = await apiClient.post(`/admin/modules/platforms/${platformId}/enable-all`);
|
||||||
|
|
||||||
|
moduleConfigLog.info('Enabled all modules:', result);
|
||||||
|
this.successMessage = `All ${result.enabled_count} modules enabled`;
|
||||||
|
|
||||||
|
// Reload module config
|
||||||
|
await this.loadModuleConfig();
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
this.successMessage = null;
|
||||||
|
}, 5000);
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
moduleConfigLog.error('Failed to enable all modules:', error);
|
||||||
|
this.error = error.message || 'Failed to enable all modules';
|
||||||
|
} finally {
|
||||||
|
this.saving = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async disableOptional() {
|
||||||
|
if (!confirm('This will disable all optional modules, keeping only core modules. Continue?')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.saving = true;
|
||||||
|
this.error = null;
|
||||||
|
this.successMessage = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const platformId = this.platform?.id;
|
||||||
|
const result = await apiClient.post(`/admin/modules/platforms/${platformId}/disable-optional`);
|
||||||
|
|
||||||
|
moduleConfigLog.info('Disabled optional modules:', result);
|
||||||
|
this.successMessage = `Optional modules disabled. Core modules kept: ${result.core_modules.join(', ')}`;
|
||||||
|
|
||||||
|
// Reload module config
|
||||||
|
await this.loadModuleConfig();
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
this.successMessage = null;
|
||||||
|
}, 5000);
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
moduleConfigLog.error('Failed to disable optional modules:', error);
|
||||||
|
this.error = error.message || 'Failed to disable optional modules';
|
||||||
|
} finally {
|
||||||
|
this.saving = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user