mirror of
https://github.com/DRYTRIX/TimeTracker.git
synced 2026-05-24 07:10:21 -05:00
083dd9f1f2
- Add centralized module registry system (ModuleRegistry) for managing module metadata, dependencies, and visibility across the application - Create module helper utilities with decorators (@module_enabled) and helper functions for route protection and template access - Add database migration (092) to add missing module visibility flags to settings and users tables for granular control - Extend Settings and User models with additional module visibility flags for CRM, Finance, Tools, and Advanced features - Implement admin module management UI for system-wide module enable/disable controls - Add module checks to routes (calendar, contacts, deals, expenses, invoices, leads, custom_reports) to enforce visibility rules - Update scheduled report service and report templates to respect module visibility settings - Bump version to 4.8.0 in setup.py - Add comprehensive documentation for module integration planning and implementation analysis
705 lines
23 KiB
Python
705 lines
23 KiB
Python
"""
|
|
Module Registry System
|
|
|
|
Centralized registry for managing module metadata, dependencies, and visibility.
|
|
"""
|
|
from dataclasses import dataclass, field
|
|
from typing import List, Optional, Dict
|
|
from enum import Enum
|
|
|
|
|
|
class ModuleCategory(Enum):
|
|
"""Module categories for organization"""
|
|
CORE = "core"
|
|
TIME_TRACKING = "time_tracking"
|
|
PROJECT_MANAGEMENT = "project_management"
|
|
CRM = "crm"
|
|
FINANCE = "finance"
|
|
INVENTORY = "inventory"
|
|
ANALYTICS = "analytics"
|
|
TOOLS = "tools"
|
|
ADMIN = "admin"
|
|
ADVANCED = "advanced"
|
|
|
|
|
|
@dataclass
|
|
class ModuleDefinition:
|
|
"""Definition of a module with its metadata and configuration"""
|
|
id: str
|
|
name: str
|
|
description: str
|
|
category: ModuleCategory
|
|
blueprint_name: str
|
|
default_enabled: bool = True
|
|
requires_admin: bool = False
|
|
dependencies: List[str] = field(default_factory=list) # Module IDs this depends on
|
|
settings_flag: Optional[str] = None # Settings.ui_allow_* field name
|
|
user_flag: Optional[str] = None # User.ui_show_* field name
|
|
routes: List[str] = field(default_factory=list) # Route endpoints
|
|
icon: Optional[str] = None # FontAwesome icon class
|
|
order: int = 0 # Display order in navigation
|
|
|
|
def __post_init__(self):
|
|
"""Validate and normalize module definition"""
|
|
if self.dependencies is None:
|
|
self.dependencies = []
|
|
if self.routes is None:
|
|
self.routes = []
|
|
|
|
|
|
class ModuleRegistry:
|
|
"""Centralized registry for all application modules"""
|
|
|
|
_modules: Dict[str, ModuleDefinition] = {}
|
|
_initialized: bool = False
|
|
|
|
@classmethod
|
|
def register(cls, module: ModuleDefinition):
|
|
"""Register a module definition"""
|
|
cls._modules[module.id] = module
|
|
|
|
@classmethod
|
|
def get(cls, module_id: str) -> Optional[ModuleDefinition]:
|
|
"""Get a module definition by ID"""
|
|
return cls._modules.get(module_id)
|
|
|
|
@classmethod
|
|
def get_all(cls) -> Dict[str, ModuleDefinition]:
|
|
"""Get all registered modules"""
|
|
return cls._modules.copy()
|
|
|
|
@classmethod
|
|
def get_by_category(cls, category: ModuleCategory) -> List[ModuleDefinition]:
|
|
"""Get all modules in a specific category, sorted by order"""
|
|
modules = [m for m in cls._modules.values() if m.category == category]
|
|
return sorted(modules, key=lambda m: m.order)
|
|
|
|
@classmethod
|
|
def is_enabled(cls, module_id: str, settings=None, user=None) -> bool:
|
|
"""
|
|
Check if a module is enabled for a user.
|
|
|
|
Args:
|
|
module_id: The module ID to check
|
|
settings: Settings instance (optional, will fetch if not provided)
|
|
user: User instance (optional, will use current_user if not provided)
|
|
|
|
Returns:
|
|
True if module is enabled, False otherwise
|
|
"""
|
|
module = cls.get(module_id)
|
|
if not module:
|
|
return False
|
|
|
|
# Core modules are always enabled
|
|
if module.category == ModuleCategory.CORE:
|
|
return True
|
|
|
|
# Admin-only modules require admin access
|
|
if module.requires_admin:
|
|
if user is None:
|
|
from flask_login import current_user
|
|
user = current_user
|
|
if not user or not getattr(user, 'is_authenticated', False):
|
|
return False
|
|
if not getattr(user, 'is_admin', False):
|
|
return False
|
|
|
|
# Fetch settings if not provided
|
|
if settings is None:
|
|
try:
|
|
from app.models import Settings
|
|
settings = Settings.get_settings()
|
|
except Exception:
|
|
# If we can't get settings, use defaults
|
|
return module.default_enabled
|
|
|
|
# Check system-wide setting
|
|
if module.settings_flag:
|
|
flag_value = getattr(settings, module.settings_flag, None)
|
|
if flag_value is False:
|
|
return False
|
|
|
|
# Fetch user if not provided
|
|
if user is None:
|
|
from flask_login import current_user
|
|
user = current_user
|
|
|
|
# Check user-specific setting
|
|
if module.user_flag and user and getattr(user, 'is_authenticated', False):
|
|
flag_value = getattr(user, module.user_flag, None)
|
|
if flag_value is False:
|
|
return False
|
|
|
|
# Check dependencies recursively
|
|
for dep_id in module.dependencies:
|
|
if not cls.is_enabled(dep_id, settings, user):
|
|
return False
|
|
|
|
return True
|
|
|
|
@classmethod
|
|
def get_enabled_modules(cls, settings=None, user=None) -> List[ModuleDefinition]:
|
|
"""Get all enabled modules for a user"""
|
|
enabled = []
|
|
for module in cls._modules.values():
|
|
if cls.is_enabled(module.id, settings, user):
|
|
enabled.append(module)
|
|
return sorted(enabled, key=lambda m: (m.category.value, m.order))
|
|
|
|
@classmethod
|
|
def initialize_defaults(cls):
|
|
"""Initialize the registry with all default module definitions"""
|
|
if cls._initialized:
|
|
return
|
|
|
|
# Core modules (always enabled)
|
|
cls.register(ModuleDefinition(
|
|
id="auth",
|
|
name="Authentication",
|
|
description="User authentication and profile management",
|
|
category=ModuleCategory.CORE,
|
|
blueprint_name="auth",
|
|
default_enabled=True,
|
|
icon="fa-user-circle",
|
|
order=0
|
|
))
|
|
|
|
cls.register(ModuleDefinition(
|
|
id="main",
|
|
name="Dashboard",
|
|
description="Main dashboard",
|
|
category=ModuleCategory.CORE,
|
|
blueprint_name="main",
|
|
default_enabled=True,
|
|
icon="fa-tachometer-alt",
|
|
order=1
|
|
))
|
|
|
|
cls.register(ModuleDefinition(
|
|
id="projects",
|
|
name="Projects",
|
|
description="Project management",
|
|
category=ModuleCategory.CORE,
|
|
blueprint_name="projects",
|
|
default_enabled=True,
|
|
icon="fa-folder",
|
|
order=2
|
|
))
|
|
|
|
cls.register(ModuleDefinition(
|
|
id="timer",
|
|
name="Time Tracking",
|
|
description="Time entry and timer management",
|
|
category=ModuleCategory.CORE,
|
|
blueprint_name="timer",
|
|
default_enabled=True,
|
|
icon="fa-clock",
|
|
order=3
|
|
))
|
|
|
|
cls.register(ModuleDefinition(
|
|
id="tasks",
|
|
name="Tasks",
|
|
description="Task management",
|
|
category=ModuleCategory.CORE,
|
|
blueprint_name="tasks",
|
|
default_enabled=True,
|
|
dependencies=["projects"],
|
|
icon="fa-tasks",
|
|
order=4
|
|
))
|
|
|
|
cls.register(ModuleDefinition(
|
|
id="clients",
|
|
name="Clients",
|
|
description="Client management",
|
|
category=ModuleCategory.CORE,
|
|
blueprint_name="clients",
|
|
default_enabled=True,
|
|
icon="fa-users",
|
|
order=5
|
|
))
|
|
|
|
# Time Tracking Features
|
|
cls.register(ModuleDefinition(
|
|
id="calendar",
|
|
name="Calendar",
|
|
description="Calendar view and integrations",
|
|
category=ModuleCategory.TIME_TRACKING,
|
|
blueprint_name="calendar",
|
|
default_enabled=True,
|
|
settings_flag="ui_allow_calendar",
|
|
user_flag="ui_show_calendar",
|
|
icon="fa-calendar-alt",
|
|
order=10
|
|
))
|
|
|
|
cls.register(ModuleDefinition(
|
|
id="project_templates",
|
|
name="Project Templates",
|
|
description="Project template system",
|
|
category=ModuleCategory.PROJECT_MANAGEMENT,
|
|
blueprint_name="project_templates",
|
|
default_enabled=True,
|
|
dependencies=["projects"],
|
|
settings_flag="ui_allow_project_templates",
|
|
user_flag="ui_show_project_templates",
|
|
icon="fa-layer-group",
|
|
order=11
|
|
))
|
|
|
|
cls.register(ModuleDefinition(
|
|
id="gantt",
|
|
name="Gantt Chart",
|
|
description="Gantt chart visualization",
|
|
category=ModuleCategory.PROJECT_MANAGEMENT,
|
|
blueprint_name="gantt",
|
|
default_enabled=True,
|
|
dependencies=["tasks"],
|
|
settings_flag="ui_allow_gantt_chart",
|
|
user_flag="ui_show_gantt_chart",
|
|
icon="fa-project-diagram",
|
|
order=12
|
|
))
|
|
|
|
cls.register(ModuleDefinition(
|
|
id="kanban",
|
|
name="Kanban Board",
|
|
description="Kanban task board",
|
|
category=ModuleCategory.PROJECT_MANAGEMENT,
|
|
blueprint_name="kanban",
|
|
default_enabled=True,
|
|
dependencies=["tasks"],
|
|
settings_flag="ui_allow_kanban_board",
|
|
user_flag="ui_show_kanban_board",
|
|
icon="fa-columns",
|
|
order=13
|
|
))
|
|
|
|
cls.register(ModuleDefinition(
|
|
id="weekly_goals",
|
|
name="Weekly Goals",
|
|
description="Weekly time goals tracking",
|
|
category=ModuleCategory.TIME_TRACKING,
|
|
blueprint_name="weekly_goals",
|
|
default_enabled=True,
|
|
settings_flag="ui_allow_weekly_goals",
|
|
user_flag="ui_show_weekly_goals",
|
|
icon="fa-bullseye",
|
|
order=14
|
|
))
|
|
|
|
cls.register(ModuleDefinition(
|
|
id="issues",
|
|
name="Issues",
|
|
description="Issue and bug tracking",
|
|
category=ModuleCategory.PROJECT_MANAGEMENT,
|
|
blueprint_name="issues",
|
|
default_enabled=True,
|
|
settings_flag="ui_allow_issues",
|
|
user_flag="ui_show_issues",
|
|
icon="fa-bug",
|
|
order=15
|
|
))
|
|
|
|
cls.register(ModuleDefinition(
|
|
id="time_entry_templates",
|
|
name="Time Entry Templates",
|
|
description="Reusable time entry templates",
|
|
category=ModuleCategory.TIME_TRACKING,
|
|
blueprint_name="time_entry_templates",
|
|
default_enabled=True,
|
|
settings_flag="ui_allow_time_entry_templates",
|
|
user_flag="ui_show_time_entry_templates",
|
|
icon="fa-clipboard-list",
|
|
order=16
|
|
))
|
|
|
|
# CRM Features
|
|
cls.register(ModuleDefinition(
|
|
id="quotes",
|
|
name="Quotes",
|
|
description="Quote management",
|
|
category=ModuleCategory.CRM,
|
|
blueprint_name="quotes",
|
|
default_enabled=True,
|
|
dependencies=["clients"],
|
|
settings_flag="ui_allow_quotes",
|
|
user_flag="ui_show_quotes",
|
|
icon="fa-file-contract",
|
|
order=20
|
|
))
|
|
|
|
cls.register(ModuleDefinition(
|
|
id="contacts",
|
|
name="Contacts",
|
|
description="Contact management",
|
|
category=ModuleCategory.CRM,
|
|
blueprint_name="contacts",
|
|
default_enabled=True,
|
|
dependencies=["clients"],
|
|
settings_flag="ui_allow_contacts",
|
|
user_flag="ui_show_contacts",
|
|
icon="fa-address-book",
|
|
order=21
|
|
))
|
|
|
|
cls.register(ModuleDefinition(
|
|
id="deals",
|
|
name="Deals",
|
|
description="Deal pipeline management",
|
|
category=ModuleCategory.CRM,
|
|
blueprint_name="deals",
|
|
default_enabled=True,
|
|
dependencies=["clients"],
|
|
settings_flag="ui_allow_deals",
|
|
user_flag="ui_show_deals",
|
|
icon="fa-handshake",
|
|
order=22
|
|
))
|
|
|
|
cls.register(ModuleDefinition(
|
|
id="leads",
|
|
name="Leads",
|
|
description="Lead management",
|
|
category=ModuleCategory.CRM,
|
|
blueprint_name="leads",
|
|
default_enabled=True,
|
|
dependencies=["clients"],
|
|
settings_flag="ui_allow_leads",
|
|
user_flag="ui_show_leads",
|
|
icon="fa-user-tag",
|
|
order=23
|
|
))
|
|
|
|
# Finance & Expenses
|
|
cls.register(ModuleDefinition(
|
|
id="reports",
|
|
name="Reports",
|
|
description="Standard reports",
|
|
category=ModuleCategory.FINANCE,
|
|
blueprint_name="reports",
|
|
default_enabled=True,
|
|
settings_flag="ui_allow_reports",
|
|
user_flag="ui_show_reports",
|
|
icon="fa-chart-bar",
|
|
order=30
|
|
))
|
|
|
|
cls.register(ModuleDefinition(
|
|
id="custom_reports",
|
|
name="Report Builder",
|
|
description="Custom report builder",
|
|
category=ModuleCategory.FINANCE,
|
|
blueprint_name="custom_reports",
|
|
default_enabled=True,
|
|
settings_flag="ui_allow_report_builder",
|
|
user_flag="ui_show_report_builder",
|
|
icon="fa-magic",
|
|
order=31
|
|
))
|
|
|
|
cls.register(ModuleDefinition(
|
|
id="scheduled_reports",
|
|
name="Scheduled Reports",
|
|
description="Automated report scheduling",
|
|
category=ModuleCategory.FINANCE,
|
|
blueprint_name="scheduled_reports",
|
|
default_enabled=True,
|
|
settings_flag="ui_allow_scheduled_reports",
|
|
user_flag="ui_show_scheduled_reports",
|
|
icon="fa-clock",
|
|
order=32
|
|
))
|
|
|
|
cls.register(ModuleDefinition(
|
|
id="invoices",
|
|
name="Invoices",
|
|
description="Invoice management",
|
|
category=ModuleCategory.FINANCE,
|
|
blueprint_name="invoices",
|
|
default_enabled=True,
|
|
dependencies=["projects"],
|
|
settings_flag="ui_allow_invoices",
|
|
user_flag="ui_show_invoices",
|
|
icon="fa-file-invoice",
|
|
order=33
|
|
))
|
|
|
|
cls.register(ModuleDefinition(
|
|
id="invoice_approvals",
|
|
name="Invoice Approvals",
|
|
description="Invoice approval workflow",
|
|
category=ModuleCategory.FINANCE,
|
|
blueprint_name="invoice_approvals",
|
|
default_enabled=True,
|
|
dependencies=["invoices"],
|
|
settings_flag="ui_allow_invoice_approvals",
|
|
user_flag="ui_show_invoice_approvals",
|
|
icon="fa-check-circle",
|
|
order=34
|
|
))
|
|
|
|
cls.register(ModuleDefinition(
|
|
id="recurring_invoices",
|
|
name="Recurring Invoices",
|
|
description="Recurring invoice management",
|
|
category=ModuleCategory.FINANCE,
|
|
blueprint_name="recurring_invoices",
|
|
default_enabled=True,
|
|
dependencies=["invoices"],
|
|
settings_flag="ui_allow_recurring_invoices",
|
|
user_flag="ui_show_recurring_invoices",
|
|
icon="fa-sync-alt",
|
|
order=35
|
|
))
|
|
|
|
cls.register(ModuleDefinition(
|
|
id="payments",
|
|
name="Payments",
|
|
description="Payment tracking",
|
|
category=ModuleCategory.FINANCE,
|
|
blueprint_name="payments",
|
|
default_enabled=True,
|
|
dependencies=["invoices"],
|
|
settings_flag="ui_allow_payments",
|
|
user_flag="ui_show_payments",
|
|
icon="fa-credit-card",
|
|
order=36
|
|
))
|
|
|
|
cls.register(ModuleDefinition(
|
|
id="payment_gateways",
|
|
name="Payment Gateways",
|
|
description="Payment gateway integration",
|
|
category=ModuleCategory.FINANCE,
|
|
blueprint_name="payment_gateways",
|
|
default_enabled=True,
|
|
dependencies=["payments"],
|
|
settings_flag="ui_allow_payment_gateways",
|
|
user_flag="ui_show_payment_gateways",
|
|
icon="fa-credit-card",
|
|
order=37
|
|
))
|
|
|
|
cls.register(ModuleDefinition(
|
|
id="expenses",
|
|
name="Expenses",
|
|
description="Expense tracking",
|
|
category=ModuleCategory.FINANCE,
|
|
blueprint_name="expenses",
|
|
default_enabled=True,
|
|
dependencies=["projects"],
|
|
settings_flag="ui_allow_expenses",
|
|
user_flag="ui_show_expenses",
|
|
icon="fa-receipt",
|
|
order=38
|
|
))
|
|
|
|
cls.register(ModuleDefinition(
|
|
id="mileage",
|
|
name="Mileage",
|
|
description="Mileage tracking",
|
|
category=ModuleCategory.FINANCE,
|
|
blueprint_name="mileage",
|
|
default_enabled=True,
|
|
settings_flag="ui_allow_mileage",
|
|
user_flag="ui_show_mileage",
|
|
icon="fa-car",
|
|
order=39
|
|
))
|
|
|
|
cls.register(ModuleDefinition(
|
|
id="per_diem",
|
|
name="Per Diem",
|
|
description="Per diem expense tracking",
|
|
category=ModuleCategory.FINANCE,
|
|
blueprint_name="per_diem",
|
|
default_enabled=True,
|
|
settings_flag="ui_allow_per_diem",
|
|
user_flag="ui_show_per_diem",
|
|
icon="fa-utensils",
|
|
order=40
|
|
))
|
|
|
|
cls.register(ModuleDefinition(
|
|
id="budget_alerts",
|
|
name="Budget Alerts",
|
|
description="Project budget monitoring",
|
|
category=ModuleCategory.FINANCE,
|
|
blueprint_name="budget_alerts",
|
|
default_enabled=True,
|
|
dependencies=["projects"],
|
|
settings_flag="ui_allow_budget_alerts",
|
|
user_flag="ui_show_budget_alerts",
|
|
icon="fa-exclamation-triangle",
|
|
order=41
|
|
))
|
|
|
|
# Inventory
|
|
cls.register(ModuleDefinition(
|
|
id="inventory",
|
|
name="Inventory",
|
|
description="Inventory management",
|
|
category=ModuleCategory.INVENTORY,
|
|
blueprint_name="inventory",
|
|
default_enabled=True,
|
|
settings_flag="ui_allow_inventory",
|
|
user_flag="ui_show_inventory",
|
|
icon="fa-boxes",
|
|
order=50
|
|
))
|
|
|
|
# Analytics
|
|
cls.register(ModuleDefinition(
|
|
id="analytics",
|
|
name="Analytics",
|
|
description="Analytics dashboard",
|
|
category=ModuleCategory.ANALYTICS,
|
|
blueprint_name="analytics",
|
|
default_enabled=True,
|
|
settings_flag="ui_allow_analytics",
|
|
user_flag="ui_show_analytics",
|
|
icon="fa-chart-line",
|
|
order=60
|
|
))
|
|
|
|
# Tools & Data
|
|
cls.register(ModuleDefinition(
|
|
id="integrations",
|
|
name="Integrations",
|
|
description="External integrations",
|
|
category=ModuleCategory.TOOLS,
|
|
blueprint_name="integrations",
|
|
default_enabled=True,
|
|
settings_flag="ui_allow_integrations",
|
|
user_flag="ui_show_integrations",
|
|
icon="fa-plug",
|
|
order=70
|
|
))
|
|
|
|
cls.register(ModuleDefinition(
|
|
id="import_export",
|
|
name="Import/Export",
|
|
description="Data import and export",
|
|
category=ModuleCategory.TOOLS,
|
|
blueprint_name="import_export",
|
|
default_enabled=True,
|
|
settings_flag="ui_allow_import_export",
|
|
user_flag="ui_show_import_export",
|
|
icon="fa-exchange-alt",
|
|
order=71
|
|
))
|
|
|
|
cls.register(ModuleDefinition(
|
|
id="saved_filters",
|
|
name="Saved Filters",
|
|
description="Saved filter management",
|
|
category=ModuleCategory.TOOLS,
|
|
blueprint_name="saved_filters",
|
|
default_enabled=True,
|
|
settings_flag="ui_allow_saved_filters",
|
|
user_flag="ui_show_saved_filters",
|
|
icon="fa-filter",
|
|
order=72
|
|
))
|
|
|
|
# Advanced Features
|
|
cls.register(ModuleDefinition(
|
|
id="workflows",
|
|
name="Workflows",
|
|
description="Automation workflows",
|
|
category=ModuleCategory.ADVANCED,
|
|
blueprint_name="workflows",
|
|
default_enabled=True,
|
|
settings_flag="ui_allow_workflows",
|
|
user_flag="ui_show_workflows",
|
|
icon="fa-sitemap",
|
|
order=80
|
|
))
|
|
|
|
cls.register(ModuleDefinition(
|
|
id="time_approvals",
|
|
name="Time Approvals",
|
|
description="Time entry approval workflow",
|
|
category=ModuleCategory.ADVANCED,
|
|
blueprint_name="time_approvals",
|
|
default_enabled=True,
|
|
dependencies=["timer"],
|
|
settings_flag="ui_allow_time_approvals",
|
|
user_flag="ui_show_time_approvals",
|
|
icon="fa-check-double",
|
|
order=81
|
|
))
|
|
|
|
cls.register(ModuleDefinition(
|
|
id="activity_feed",
|
|
name="Activity Feed",
|
|
description="Activity stream",
|
|
category=ModuleCategory.ADVANCED,
|
|
blueprint_name="activity_feed",
|
|
default_enabled=True,
|
|
settings_flag="ui_allow_activity_feed",
|
|
user_flag="ui_show_activity_feed",
|
|
icon="fa-stream",
|
|
order=82
|
|
))
|
|
|
|
cls.register(ModuleDefinition(
|
|
id="recurring_tasks",
|
|
name="Recurring Tasks",
|
|
description="Automated recurring tasks",
|
|
category=ModuleCategory.ADVANCED,
|
|
blueprint_name="recurring_tasks",
|
|
default_enabled=True,
|
|
dependencies=["tasks"],
|
|
settings_flag="ui_allow_recurring_tasks",
|
|
user_flag="ui_show_recurring_tasks",
|
|
icon="fa-redo",
|
|
order=83
|
|
))
|
|
|
|
cls.register(ModuleDefinition(
|
|
id="team_chat",
|
|
name="Team Chat",
|
|
description="Team messaging",
|
|
category=ModuleCategory.ADVANCED,
|
|
blueprint_name="team_chat",
|
|
default_enabled=True,
|
|
settings_flag="ui_allow_team_chat",
|
|
user_flag="ui_show_team_chat",
|
|
icon="fa-comments",
|
|
order=84
|
|
))
|
|
|
|
cls.register(ModuleDefinition(
|
|
id="client_portal",
|
|
name="Client Portal",
|
|
description="Client-facing portal",
|
|
category=ModuleCategory.ADVANCED,
|
|
blueprint_name="client_portal",
|
|
default_enabled=True,
|
|
dependencies=["clients"],
|
|
settings_flag="ui_allow_client_portal",
|
|
user_flag="ui_show_client_portal",
|
|
icon="fa-door-open",
|
|
order=85
|
|
))
|
|
|
|
cls.register(ModuleDefinition(
|
|
id="kiosk",
|
|
name="Kiosk Mode",
|
|
description="Kiosk interface",
|
|
category=ModuleCategory.ADVANCED,
|
|
blueprint_name="kiosk",
|
|
default_enabled=True,
|
|
settings_flag="ui_allow_kiosk",
|
|
user_flag="ui_show_kiosk",
|
|
icon="fa-desktop",
|
|
order=86
|
|
))
|
|
|
|
cls._initialized = True
|
|
|