feat(admin): added live configuration management, where user can enable/disable and change configurations without editing .env file. Some changes will need an application restart
This commit is contained in:
@@ -69,6 +69,13 @@
|
||||
- URL parameters take priority over configuration (e.g., `?open_database=1`)
|
||||
- Database section expanded by default to maintain original behavior
|
||||
- Smart metadata handling: sub-section expansion automatically expands parent metadata section
|
||||
- Add live environment variable configuration management system
|
||||
- Configuration Management interface in admin panel with live preview and badge system
|
||||
- Live settings: Can be changed without application restart (menu visibility, table display, pagination, features)
|
||||
- Static settings: Require restart but can be edited and saved to .env file (authentication, server, database, API keys)
|
||||
- Advanced badge system showing value status: True/False for booleans, Set/Default/Unset for other values, Changed indicator
|
||||
- Live API endpoints: `/admin/api/config/update` for immediate changes, `/admin/api/config/update-static` for .env updates
|
||||
- Form pre-population with current values and automatic page reload after successful live updates
|
||||
- Add performance optimization
|
||||
- SQLite WAL Mode:
|
||||
- Increased cache size to 10,000 pages (~40MB) for faster query execution
|
||||
|
||||
@@ -0,0 +1,309 @@
|
||||
import os
|
||||
import logging
|
||||
from typing import Any, Dict, Final, List, Optional
|
||||
from pathlib import Path
|
||||
from flask import current_app
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# Environment variables that can be changed live without restart
|
||||
LIVE_CHANGEABLE_VARS: Final[List[str]] = [
|
||||
'BK_BRICKLINK_LINKS',
|
||||
'BK_DEFAULT_TABLE_PER_PAGE',
|
||||
'BK_INDEPENDENT_ACCORDIONS',
|
||||
'BK_HIDE_ADD_SET',
|
||||
'BK_HIDE_ADD_BULK_SET',
|
||||
'BK_HIDE_ADMIN',
|
||||
'BK_ADMIN_DEFAULT_EXPANDED_SECTIONS',
|
||||
'BK_HIDE_ALL_INSTRUCTIONS',
|
||||
'BK_HIDE_ALL_MINIFIGURES',
|
||||
'BK_HIDE_ALL_PARTS',
|
||||
'BK_HIDE_ALL_PROBLEMS_PARTS',
|
||||
'BK_HIDE_ALL_SETS',
|
||||
'BK_HIDE_ALL_STORAGES',
|
||||
'BK_HIDE_STATISTICS',
|
||||
'BK_HIDE_SET_INSTRUCTIONS',
|
||||
'BK_HIDE_TABLE_DAMAGED_PARTS',
|
||||
'BK_HIDE_TABLE_MISSING_PARTS',
|
||||
'BK_HIDE_TABLE_CHECKED_PARTS',
|
||||
'BK_HIDE_WISHES',
|
||||
'BK_MINIFIGURES_PAGINATION_SIZE_DESKTOP',
|
||||
'BK_MINIFIGURES_PAGINATION_SIZE_MOBILE',
|
||||
'BK_MINIFIGURES_SERVER_SIDE_PAGINATION',
|
||||
'BK_PARTS_PAGINATION_SIZE_DESKTOP',
|
||||
'BK_PARTS_PAGINATION_SIZE_MOBILE',
|
||||
'BK_PARTS_SERVER_SIDE_PAGINATION',
|
||||
'BK_SETS_SERVER_SIDE_PAGINATION',
|
||||
'BK_PROBLEMS_PAGINATION_SIZE_DESKTOP',
|
||||
'BK_PROBLEMS_PAGINATION_SIZE_MOBILE',
|
||||
'BK_PROBLEMS_SERVER_SIDE_PAGINATION',
|
||||
'BK_SETS_PAGINATION_SIZE_DESKTOP',
|
||||
'BK_SETS_PAGINATION_SIZE_MOBILE',
|
||||
'BK_SETS_CONSOLIDATION',
|
||||
'BK_RANDOM',
|
||||
'BK_REBRICKABLE_LINKS',
|
||||
'BK_SHOW_GRID_FILTERS',
|
||||
'BK_SHOW_GRID_SORT',
|
||||
'BK_SKIP_SPARE_PARTS',
|
||||
'BK_USE_REMOTE_IMAGES',
|
||||
'BK_PEERON_DOWNLOAD_DELAY',
|
||||
'BK_PEERON_MIN_IMAGE_SIZE',
|
||||
'BK_REBRICKABLE_PAGE_SIZE',
|
||||
'BK_STATISTICS_SHOW_CHARTS',
|
||||
'BK_STATISTICS_DEFAULT_EXPANDED',
|
||||
# Default ordering and formatting
|
||||
'BK_INSTRUCTIONS_ALLOWED_EXTENSIONS',
|
||||
'BK_MINIFIGURES_DEFAULT_ORDER',
|
||||
'BK_PARTS_DEFAULT_ORDER',
|
||||
'BK_SETS_DEFAULT_ORDER',
|
||||
'BK_PURCHASE_LOCATION_DEFAULT_ORDER',
|
||||
'BK_STORAGE_DEFAULT_ORDER',
|
||||
'BK_WISHES_DEFAULT_ORDER',
|
||||
# URL and Pattern Variables
|
||||
'BK_BRICKLINK_LINK_PART_PATTERN',
|
||||
'BK_REBRICKABLE_IMAGE_NIL',
|
||||
'BK_REBRICKABLE_IMAGE_NIL_MINIFIGURE',
|
||||
'BK_REBRICKABLE_LINK_MINIFIGURE_PATTERN',
|
||||
'BK_REBRICKABLE_LINK_PART_PATTERN',
|
||||
'BK_REBRICKABLE_LINK_INSTRUCTIONS_PATTERN',
|
||||
'BK_PEERON_INSTRUCTION_PATTERN',
|
||||
'BK_PEERON_SCAN_PATTERN',
|
||||
'BK_PEERON_THUMBNAIL_PATTERN',
|
||||
'BK_RETIRED_SETS_FILE_URL',
|
||||
'BK_RETIRED_SETS_PATH',
|
||||
'BK_THEMES_FILE_URL',
|
||||
'BK_THEMES_PATH'
|
||||
]
|
||||
|
||||
# Environment variables that require restart
|
||||
RESTART_REQUIRED_VARS: Final[List[str]] = [
|
||||
'BK_AUTHENTICATION_PASSWORD',
|
||||
'BK_AUTHENTICATION_KEY',
|
||||
'BK_DATABASE_PATH',
|
||||
'BK_DEBUG',
|
||||
'BK_DOMAIN_NAME',
|
||||
'BK_HOST',
|
||||
'BK_PORT',
|
||||
'BK_SOCKET_NAMESPACE',
|
||||
'BK_SOCKET_PATH',
|
||||
'BK_NO_THREADED_SOCKET',
|
||||
'BK_TIMEZONE',
|
||||
'BK_REBRICKABLE_API_KEY',
|
||||
'BK_INSTRUCTIONS_FOLDER',
|
||||
'BK_PARTS_FOLDER',
|
||||
'BK_SETS_FOLDER',
|
||||
'BK_MINIFIGURES_FOLDER',
|
||||
'BK_DATABASE_TIMESTAMP_FORMAT',
|
||||
'BK_FILE_DATETIME_FORMAT',
|
||||
'BK_PURCHASE_DATE_FORMAT',
|
||||
'BK_PURCHASE_CURRENCY',
|
||||
'BK_REBRICKABLE_USER_AGENT',
|
||||
'BK_USER_AGENT'
|
||||
]
|
||||
|
||||
class ConfigManager:
|
||||
"""Manages live configuration updates for BrickTracker"""
|
||||
|
||||
def __init__(self):
|
||||
self.env_file_path = Path('.env')
|
||||
|
||||
def get_current_config(self) -> Dict[str, Any]:
|
||||
"""Get current configuration values for live-changeable variables"""
|
||||
config = {}
|
||||
for var in LIVE_CHANGEABLE_VARS:
|
||||
# Get internal config name
|
||||
internal_name = var.replace('BK_', '')
|
||||
# Get current value from Flask config
|
||||
if internal_name in current_app.config:
|
||||
config[var] = current_app.config[internal_name]
|
||||
else:
|
||||
# Fallback to environment variable
|
||||
config[var] = os.environ.get(var, '')
|
||||
return config
|
||||
|
||||
def get_restart_required_config(self) -> Dict[str, Any]:
|
||||
"""Get current configuration values for restart-required variables"""
|
||||
config = {}
|
||||
for var in RESTART_REQUIRED_VARS:
|
||||
# Get internal config name
|
||||
internal_name = var.replace('BK_', '')
|
||||
# Get current value from Flask config
|
||||
if internal_name in current_app.config:
|
||||
config[var] = current_app.config[internal_name]
|
||||
else:
|
||||
# Fallback to environment variable
|
||||
config[var] = os.environ.get(var, '')
|
||||
return config
|
||||
|
||||
def update_config(self, updates: Dict[str, Any]) -> Dict[str, str]:
|
||||
"""Update configuration values. Returns dict with status for each update"""
|
||||
results = {}
|
||||
for var_name, new_value in updates.items():
|
||||
if var_name not in LIVE_CHANGEABLE_VARS:
|
||||
results[var_name] = f"Error: {var_name} requires restart to change"
|
||||
continue
|
||||
|
||||
try:
|
||||
# Update environment variable
|
||||
os.environ[var_name] = str(new_value)
|
||||
# Update Flask config
|
||||
internal_name = var_name.replace('BK_', '')
|
||||
cast_value = self._cast_value(var_name, new_value)
|
||||
current_app.config[internal_name] = cast_value
|
||||
# Update .env file
|
||||
self._update_env_file(var_name, new_value)
|
||||
results[var_name] = "Updated successfully"
|
||||
logger.info(f"Config updated: {var_name}={new_value}")
|
||||
except Exception as e:
|
||||
results[var_name] = f"Error: {str(e)}"
|
||||
logger.error(f"Failed to update {var_name}: {e}")
|
||||
return results
|
||||
|
||||
def _cast_value(self, var_name: str, value: Any) -> Any:
|
||||
"""Cast value to appropriate type based on variable name"""
|
||||
# List variables (admin sections) - Check this FIRST before boolean check
|
||||
if 'sections' in var_name.lower():
|
||||
if isinstance(value, str):
|
||||
return [section.strip() for section in value.split(',') if section.strip()]
|
||||
elif isinstance(value, list):
|
||||
return value
|
||||
else:
|
||||
return []
|
||||
# Integer variables (pagination sizes, delays, etc.) - Check BEFORE boolean check
|
||||
if any(keyword in var_name.lower() for keyword in ['_size', '_page', 'delay', 'min_', 'per_page', 'page_size']):
|
||||
try:
|
||||
return int(value)
|
||||
except (ValueError, TypeError):
|
||||
return 0
|
||||
# Boolean variables - More specific patterns to avoid conflicts
|
||||
if any(keyword in var_name.lower() for keyword in ['hide_', 'server_side_pagination', '_links', 'random', 'skip_', 'show_', 'use_', '_consolidation', '_charts', '_expanded']):
|
||||
if isinstance(value, str):
|
||||
return value.lower() in ('true', '1', 'yes', 'on')
|
||||
return bool(value)
|
||||
# String variables (default)
|
||||
return str(value)
|
||||
|
||||
def _format_env_value(self, value: Any) -> str:
|
||||
"""Format value for .env file storage"""
|
||||
if isinstance(value, bool):
|
||||
return 'true' if value else 'false'
|
||||
elif isinstance(value, (int, float)):
|
||||
return str(value)
|
||||
elif isinstance(value, list):
|
||||
return ','.join(str(item) for item in value)
|
||||
elif value is None:
|
||||
return ''
|
||||
else:
|
||||
return str(value)
|
||||
|
||||
def _update_env_file(self, var_name: str, value: Any) -> None:
|
||||
"""Update the .env file with new value"""
|
||||
if not self.env_file_path.exists():
|
||||
self.env_file_path.touch()
|
||||
|
||||
# Read current .env content
|
||||
lines = []
|
||||
if self.env_file_path.exists():
|
||||
with open(self.env_file_path, 'r', encoding='utf-8') as f:
|
||||
lines = f.readlines()
|
||||
|
||||
# Format value for .env file
|
||||
env_value = self._format_env_value(value)
|
||||
|
||||
# Find and update the line, or add new line
|
||||
updated = False
|
||||
|
||||
# First pass: Look for existing active variable
|
||||
for i, line in enumerate(lines):
|
||||
if line.strip().startswith(f"{var_name}="):
|
||||
lines[i] = f"{var_name}={env_value}\n"
|
||||
updated = True
|
||||
break
|
||||
|
||||
# Second pass: If not found, look for commented-out variable
|
||||
if not updated:
|
||||
for i, line in enumerate(lines):
|
||||
stripped = line.strip()
|
||||
# Check for commented-out variable: # BK_VAR= or #BK_VAR=
|
||||
if stripped.startswith('#') and var_name in stripped:
|
||||
# Extract the part after #, handling optional space
|
||||
comment_content = stripped[1:].strip()
|
||||
if comment_content.startswith(f"{var_name}=") or comment_content.startswith(f"{var_name} ="):
|
||||
# Uncomment and set new value, preserving any leading whitespace from original line
|
||||
leading_whitespace = line[:len(line) - len(line.lstrip())]
|
||||
lines[i] = f"{leading_whitespace}{var_name}={env_value}\n"
|
||||
updated = True
|
||||
logger.info(f"Uncommented and updated {var_name} in .env file")
|
||||
break
|
||||
|
||||
# Third pass: If still not found, append to end
|
||||
if not updated:
|
||||
lines.append(f"{var_name}={env_value}\n")
|
||||
logger.info(f"Added new {var_name} to end of .env file")
|
||||
|
||||
# Write back to file
|
||||
with open(self.env_file_path, 'w', encoding='utf-8') as f:
|
||||
f.writelines(lines)
|
||||
|
||||
def validate_config(self) -> Dict[str, Any]:
|
||||
"""Validate current configuration"""
|
||||
issues = []
|
||||
warnings = []
|
||||
|
||||
# Check if critical variables are set
|
||||
if not os.environ.get('BK_REBRICKABLE_API_KEY'):
|
||||
warnings.append("BK_REBRICKABLE_API_KEY not set - some features may not work")
|
||||
|
||||
# Check for conflicting settings
|
||||
if (os.environ.get('BK_PARTS_SERVER_SIDE_PAGINATION', '').lower() == 'false' and
|
||||
int(os.environ.get('BK_PARTS_PAGINATION_SIZE_DESKTOP', '10')) > 100):
|
||||
warnings.append("Large pagination size with client-side pagination may cause performance issues")
|
||||
|
||||
# Check pagination sizes are reasonable
|
||||
for var in ['BK_SETS_PAGINATION_SIZE_DESKTOP', 'BK_PARTS_PAGINATION_SIZE_DESKTOP', 'BK_MINIFIGURES_PAGINATION_SIZE_DESKTOP']:
|
||||
try:
|
||||
size = int(os.environ.get(var, '10'))
|
||||
if size < 1:
|
||||
issues.append(f"{var} must be at least 1")
|
||||
elif size > 1000:
|
||||
warnings.append(f"{var} is very large ({size}) - may cause performance issues")
|
||||
except ValueError:
|
||||
issues.append(f"{var} must be a valid integer")
|
||||
|
||||
return {
|
||||
'issues': issues,
|
||||
'warnings': warnings,
|
||||
'status': 'valid' if not issues else 'has_issues'
|
||||
}
|
||||
|
||||
def get_variable_help(self, var_name: str) -> str:
|
||||
"""Get help text for a configuration variable"""
|
||||
help_text = {
|
||||
'BK_BRICKLINK_LINKS': 'Show BrickLink links throughout the application',
|
||||
'BK_DEFAULT_TABLE_PER_PAGE': 'Default number of items per page in tables',
|
||||
'BK_INDEPENDENT_ACCORDIONS': 'Make accordion sections independent (can open multiple)',
|
||||
'BK_HIDE_ADD_SET': 'Hide the "Add Set" menu entry',
|
||||
'BK_HIDE_ADD_BULK_SET': 'Hide the "Add Bulk Set" menu entry',
|
||||
'BK_HIDE_ADMIN': 'Hide the "Admin" menu entry',
|
||||
'BK_ADMIN_DEFAULT_EXPANDED_SECTIONS': 'Admin sections to expand by default (comma-separated)',
|
||||
'BK_HIDE_ALL_INSTRUCTIONS': 'Hide the "Instructions" menu entry',
|
||||
'BK_HIDE_ALL_MINIFIGURES': 'Hide the "Minifigures" menu entry',
|
||||
'BK_HIDE_ALL_PARTS': 'Hide the "Parts" menu entry',
|
||||
'BK_HIDE_ALL_PROBLEMS_PARTS': 'Hide the "Problems" menu entry',
|
||||
'BK_HIDE_ALL_SETS': 'Hide the "Sets" menu entry',
|
||||
'BK_HIDE_ALL_STORAGES': 'Hide the "Storages" menu entry',
|
||||
'BK_HIDE_STATISTICS': 'Hide the "Statistics" menu entry',
|
||||
'BK_HIDE_SET_INSTRUCTIONS': 'Hide instructions section in set details',
|
||||
'BK_HIDE_TABLE_DAMAGED_PARTS': 'Hide the "Damaged" column in parts tables',
|
||||
'BK_HIDE_TABLE_MISSING_PARTS': 'Hide the "Missing" column in parts tables',
|
||||
'BK_HIDE_TABLE_CHECKED_PARTS': 'Hide the "Checked" column in parts tables',
|
||||
'BK_HIDE_WISHES': 'Hide the "Wishes" menu entry',
|
||||
'BK_SETS_CONSOLIDATION': 'Enable set consolidation/grouping functionality',
|
||||
'BK_SHOW_GRID_FILTERS': 'Show filter options on grids by default',
|
||||
'BK_SHOW_GRID_SORT': 'Show sort options on grids by default',
|
||||
'BK_SKIP_SPARE_PARTS': 'Skip spare parts when importing sets',
|
||||
'BK_USE_REMOTE_IMAGES': 'Use remote images from Rebrickable CDN instead of local',
|
||||
'BK_STATISTICS_SHOW_CHARTS': 'Show collection growth charts on statistics page',
|
||||
'BK_STATISTICS_DEFAULT_EXPANDED': 'Expand all statistics sections by default'
|
||||
}
|
||||
return help_text.get(var_name, 'No help available for this variable')
|
||||
@@ -1,9 +1,11 @@
|
||||
import logging
|
||||
|
||||
from flask import Blueprint, request, render_template, current_app
|
||||
from flask import Blueprint, request, render_template, current_app, jsonify
|
||||
from flask_login import login_required
|
||||
|
||||
from ...configuration_list import BrickConfigurationList
|
||||
from ...config_manager import ConfigManager
|
||||
from ...config import CONFIG
|
||||
from ..exceptions import exception_handler
|
||||
from ...instructions_list import BrickInstructionsList
|
||||
from ...rebrickable_image import RebrickableImage
|
||||
@@ -27,6 +29,68 @@ logger = logging.getLogger(__name__)
|
||||
admin_page = Blueprint('admin', __name__, url_prefix='/admin')
|
||||
|
||||
|
||||
def get_env_values():
|
||||
"""Get current environment values, using defaults from config when not set"""
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
env_values = {}
|
||||
config_defaults = {}
|
||||
env_explicit_values = {} # Track which values are explicitly set
|
||||
|
||||
# Read .env file if it exists
|
||||
env_file = Path('.env')
|
||||
env_from_file = {}
|
||||
if env_file.exists():
|
||||
with open(env_file, 'r', encoding='utf-8') as f:
|
||||
for line in f:
|
||||
line = line.strip()
|
||||
if line and not line.startswith('#') and '=' in line:
|
||||
key, value = line.split('=', 1)
|
||||
env_from_file[key] = value
|
||||
|
||||
# Process each config item
|
||||
for config_item in CONFIG:
|
||||
env_name = f"BK_{config_item['n']}"
|
||||
|
||||
# Store default value (with casting applied)
|
||||
default_value = config_item.get('d', '')
|
||||
if 'c' in config_item and default_value is not None:
|
||||
cast_type = config_item['c']
|
||||
if cast_type == bool and default_value == '':
|
||||
default_value = False # Default for booleans is False only if no default specified
|
||||
elif cast_type == list and isinstance(default_value, str):
|
||||
default_value = [item.strip() for item in default_value.split(',') if item.strip()]
|
||||
# For int/other types, keep the original default value
|
||||
config_defaults[env_name] = default_value
|
||||
|
||||
# Check if value is explicitly set in .env file or environment
|
||||
is_explicitly_set = env_name in env_from_file or env_name in os.environ
|
||||
env_explicit_values[env_name] = is_explicitly_set
|
||||
|
||||
# Get value from .env file, environment, or default
|
||||
value = env_from_file.get(env_name) or os.environ.get(env_name)
|
||||
if value is None:
|
||||
value = default_value
|
||||
else:
|
||||
# Apply casting if specified
|
||||
if 'c' in config_item and value is not None:
|
||||
cast_type = config_item['c']
|
||||
if cast_type == bool and isinstance(value, str):
|
||||
value = value.lower() in ('true', '1', 'yes', 'on')
|
||||
elif cast_type == int and value != '':
|
||||
try:
|
||||
value = int(value)
|
||||
except (ValueError, TypeError):
|
||||
value = config_item.get('d', 0)
|
||||
elif cast_type == list and isinstance(value, str):
|
||||
value = [item.strip() for item in value.split(',') if item.strip()]
|
||||
|
||||
env_values[env_name] = value
|
||||
|
||||
return env_values, config_defaults, env_explicit_values
|
||||
|
||||
|
||||
# Admin
|
||||
@admin_page.route('/', methods=['GET'])
|
||||
@login_required
|
||||
@@ -138,9 +202,13 @@ def admin() -> str:
|
||||
open_tag
|
||||
)
|
||||
|
||||
env_values, config_defaults, env_explicit_values = get_env_values()
|
||||
return render_template(
|
||||
'admin.html',
|
||||
configuration=BrickConfigurationList.list(),
|
||||
env_values=env_values,
|
||||
config_defaults=config_defaults,
|
||||
env_explicit_values=env_explicit_values,
|
||||
database_counters=database_counters,
|
||||
database_error=request.args.get('database_error'),
|
||||
database_exception=database_exception,
|
||||
@@ -176,3 +244,103 @@ def admin() -> str:
|
||||
tag_error=request.args.get('tag_error'),
|
||||
theme=BrickThemeList(),
|
||||
)
|
||||
|
||||
|
||||
# API Endpoints for Configuration Management
|
||||
|
||||
@admin_page.route('/api/config/update', methods=['POST'])
|
||||
@login_required
|
||||
@exception_handler(__file__)
|
||||
def update_config() -> str:
|
||||
"""Update live configuration variables"""
|
||||
try:
|
||||
data = request.get_json()
|
||||
if not data:
|
||||
return jsonify({
|
||||
'status': 'error',
|
||||
'message': 'No JSON data provided'
|
||||
}), 400
|
||||
|
||||
updates = data.get('updates', {})
|
||||
if not updates:
|
||||
return jsonify({
|
||||
'status': 'error',
|
||||
'message': 'No updates provided'
|
||||
}), 400
|
||||
|
||||
# Use ConfigManager to update live configuration
|
||||
config_manager = ConfigManager()
|
||||
results = config_manager.update_config(updates)
|
||||
|
||||
# Check if all updates were successful
|
||||
successful_updates = {k: v for k, v in results.items() if "successfully" in v}
|
||||
failed_updates = {k: v for k, v in results.items() if "successfully" not in v}
|
||||
|
||||
logger.info(f"Configuration update: {len(successful_updates)} successful, {len(failed_updates)} failed")
|
||||
|
||||
if failed_updates:
|
||||
logger.warning(f"Failed updates: {failed_updates}")
|
||||
|
||||
return jsonify({
|
||||
'status': 'success' if not failed_updates else 'partial',
|
||||
'results': results,
|
||||
'successful_count': len(successful_updates),
|
||||
'failed_count': len(failed_updates)
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error updating configuration: {e}")
|
||||
return jsonify({
|
||||
'status': 'error',
|
||||
'message': str(e)
|
||||
}), 500
|
||||
|
||||
|
||||
@admin_page.route('/api/config/update-static', methods=['POST'])
|
||||
@login_required
|
||||
@exception_handler(__file__)
|
||||
def update_static_config() -> str:
|
||||
"""Update static configuration variables (requires restart)"""
|
||||
try:
|
||||
data = request.get_json()
|
||||
if not data:
|
||||
return jsonify({
|
||||
'status': 'error',
|
||||
'message': 'No JSON data provided'
|
||||
}), 400
|
||||
|
||||
updates = data.get('updates', {})
|
||||
if not updates:
|
||||
return jsonify({
|
||||
'status': 'error',
|
||||
'message': 'No updates provided'
|
||||
}), 400
|
||||
|
||||
# Use ConfigManager to update .env file
|
||||
config_manager = ConfigManager()
|
||||
|
||||
# Update each variable in the .env file
|
||||
updated_count = 0
|
||||
for var_name, value in updates.items():
|
||||
try:
|
||||
config_manager._update_env_file(var_name, value)
|
||||
updated_count += 1
|
||||
logger.info(f"Updated static config: {var_name}")
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to update static config {var_name}: {e}")
|
||||
raise e
|
||||
|
||||
logger.info(f"Updated {updated_count} static configuration variables")
|
||||
|
||||
return jsonify({
|
||||
'status': 'success',
|
||||
'message': f'Successfully updated {updated_count} static configuration variables to .env file',
|
||||
'updated_count': updated_count
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error updating static configuration: {e}")
|
||||
return jsonify({
|
||||
'status': 'error',
|
||||
'message': str(e)
|
||||
}), 500
|
||||
|
||||
@@ -0,0 +1,325 @@
|
||||
// Admin Configuration Management
|
||||
// Handles live environment variable configuration interface
|
||||
|
||||
// Initialize form values with current configuration
|
||||
function initializeConfigValues() {
|
||||
console.log('Initializing config values with:', window.CURRENT_CONFIG);
|
||||
|
||||
Object.keys(window.CURRENT_CONFIG).forEach(varName => {
|
||||
const value = window.CURRENT_CONFIG[varName];
|
||||
console.log(`Setting ${varName} = ${value}`);
|
||||
|
||||
// Handle live settings (checkboxes and inputs)
|
||||
const liveToggle = document.getElementById(varName);
|
||||
if (liveToggle && liveToggle.type === 'checkbox') {
|
||||
liveToggle.checked = value === true;
|
||||
console.log(`Set checkbox ${varName} to ${value}`);
|
||||
}
|
||||
|
||||
const liveInputs = document.querySelectorAll(`input[data-var="${varName}"]:not(.config-static)`);
|
||||
liveInputs.forEach(input => {
|
||||
if (input.type !== 'checkbox') {
|
||||
input.value = value !== null && value !== undefined ? value : '';
|
||||
console.log(`Set input ${varName} to ${input.value}`);
|
||||
}
|
||||
});
|
||||
|
||||
// Handle static settings
|
||||
const staticToggle = document.getElementById(`static-${varName}`);
|
||||
if (staticToggle && staticToggle.type === 'checkbox') {
|
||||
staticToggle.checked = value === true;
|
||||
console.log(`Set static checkbox ${varName} to ${value}`);
|
||||
}
|
||||
|
||||
const staticInputs = document.querySelectorAll(`input[data-var="${varName}"].config-static`);
|
||||
staticInputs.forEach(input => {
|
||||
if (input.type !== 'checkbox') {
|
||||
input.value = value !== null && value !== undefined ? value : '';
|
||||
console.log(`Set static input ${varName} to ${input.value}`);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Handle config change events
|
||||
function handleConfigChange(element) {
|
||||
const varName = element.dataset.var;
|
||||
let newValue;
|
||||
|
||||
if (element.type === 'checkbox') {
|
||||
newValue = element.checked;
|
||||
} else if (element.type === 'number') {
|
||||
newValue = parseInt(element.value) || 0;
|
||||
} else {
|
||||
newValue = element.value;
|
||||
}
|
||||
|
||||
// Update the badge display
|
||||
updateConfigBadge(varName, newValue);
|
||||
|
||||
// Note: Changes are only saved when "Save All Changes" button is clicked
|
||||
}
|
||||
|
||||
// Update badge display
|
||||
function updateConfigBadge(varName, value) {
|
||||
const defaultValue = window.DEFAULT_CONFIG[varName];
|
||||
const isChanged = JSON.stringify(value) !== JSON.stringify(defaultValue);
|
||||
|
||||
// Remove existing badges but keep them inline
|
||||
const existingBadges = document.querySelectorAll(`[data-badge-var="${varName}"]`);
|
||||
existingBadges.forEach(badge => {
|
||||
badge.remove();
|
||||
});
|
||||
|
||||
// Find the label where we should insert new badges
|
||||
const label = document.querySelector(`label[for="${varName}"], label[for="static-${varName}"]`);
|
||||
if (!label) return;
|
||||
|
||||
// Find the description div (with .text-muted class) to insert badges before it
|
||||
const descriptionDiv = label.querySelector('.text-muted');
|
||||
|
||||
// Create value badge based on new logic
|
||||
let valueBadge;
|
||||
if (value === true) {
|
||||
valueBadge = document.createElement('span');
|
||||
valueBadge.className = 'badge rounded-pill text-bg-success ms-2';
|
||||
valueBadge.textContent = 'True';
|
||||
valueBadge.setAttribute('data-badge-var', varName);
|
||||
valueBadge.setAttribute('data-badge-type', 'value');
|
||||
} else if (value === false) {
|
||||
valueBadge = document.createElement('span');
|
||||
valueBadge.className = 'badge rounded-pill text-bg-danger ms-2';
|
||||
valueBadge.textContent = 'False';
|
||||
valueBadge.setAttribute('data-badge-var', varName);
|
||||
valueBadge.setAttribute('data-badge-type', 'value');
|
||||
} else if (JSON.stringify(value) === JSON.stringify(defaultValue)) {
|
||||
valueBadge = document.createElement('span');
|
||||
valueBadge.className = 'badge rounded-pill text-bg-light text-dark ms-2';
|
||||
valueBadge.textContent = `Default: ${defaultValue}`;
|
||||
valueBadge.setAttribute('data-badge-var', varName);
|
||||
valueBadge.setAttribute('data-badge-type', 'value');
|
||||
} else {
|
||||
// For text/number fields that have been changed, show "Default: X"
|
||||
valueBadge = document.createElement('span');
|
||||
valueBadge.className = 'badge rounded-pill text-bg-light text-dark ms-2';
|
||||
valueBadge.textContent = `Default: ${defaultValue}`;
|
||||
valueBadge.setAttribute('data-badge-var', varName);
|
||||
valueBadge.setAttribute('data-badge-type', 'value');
|
||||
}
|
||||
|
||||
// Insert badge before the description div (to keep it on same line as title)
|
||||
if (descriptionDiv) {
|
||||
label.insertBefore(valueBadge, descriptionDiv);
|
||||
} else {
|
||||
label.appendChild(valueBadge);
|
||||
}
|
||||
|
||||
// Add changed badge if needed
|
||||
if (isChanged) {
|
||||
const changedBadge = document.createElement('span');
|
||||
changedBadge.className = 'badge rounded-pill text-bg-warning ms-1';
|
||||
changedBadge.textContent = 'Changed';
|
||||
changedBadge.setAttribute('data-badge-var', varName);
|
||||
changedBadge.setAttribute('data-badge-type', 'changed');
|
||||
|
||||
// Insert changed badge after the value badge
|
||||
if (descriptionDiv) {
|
||||
label.insertBefore(changedBadge, descriptionDiv);
|
||||
} else {
|
||||
label.appendChild(changedBadge);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Handle static config save
|
||||
function saveStaticConfig() {
|
||||
const staticInputs = document.querySelectorAll('.config-static, .config-static-toggle');
|
||||
const updates = {};
|
||||
|
||||
staticInputs.forEach(input => {
|
||||
const varName = input.dataset.var;
|
||||
let value;
|
||||
|
||||
if (input.type === 'checkbox') {
|
||||
value = input.checked;
|
||||
} else {
|
||||
value = input.value;
|
||||
}
|
||||
|
||||
updates[varName] = value;
|
||||
});
|
||||
|
||||
console.log('Saving static config:', updates);
|
||||
|
||||
// Send to backend via fetch API
|
||||
fetch('/admin/api/config/update-static', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({ updates: updates })
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
const statusContainer = document.getElementById('config-status');
|
||||
if (statusContainer) {
|
||||
if (data.status === 'success') {
|
||||
statusContainer.innerHTML = '<div class="alert alert-success"><i class="ri-check-line"></i> Static configuration saved to .env file!</div>';
|
||||
setTimeout(() => {
|
||||
statusContainer.innerHTML = '';
|
||||
}, 3000);
|
||||
} else {
|
||||
statusContainer.innerHTML = `<div class="alert alert-danger"><i class="ri-error-warning-line"></i> Error: ${data.message || 'Failed to save static configuration'}</div>`;
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Save static config error:', error);
|
||||
const statusContainer = document.getElementById('config-status');
|
||||
if (statusContainer) {
|
||||
statusContainer.innerHTML = '<div class="alert alert-danger"><i class="ri-error-warning-line"></i> Error: Failed to save static configuration</div>';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Handle button functionality
|
||||
function setupButtonHandlers() {
|
||||
// Save All Changes button
|
||||
const saveAllBtn = document.getElementById('config-save-all');
|
||||
if (saveAllBtn) {
|
||||
saveAllBtn.addEventListener('click', () => {
|
||||
console.log('Save All Changes clicked');
|
||||
saveLiveConfiguration();
|
||||
});
|
||||
}
|
||||
|
||||
// Refresh button
|
||||
const refreshBtn = document.getElementById('config-refresh');
|
||||
if (refreshBtn) {
|
||||
refreshBtn.addEventListener('click', () => {
|
||||
console.log('Refresh clicked');
|
||||
location.reload();
|
||||
});
|
||||
}
|
||||
|
||||
// Reset button
|
||||
const resetBtn = document.getElementById('config-reset');
|
||||
if (resetBtn) {
|
||||
resetBtn.addEventListener('click', () => {
|
||||
console.log('Reset clicked');
|
||||
if (confirm('Are you sure you want to reset all settings to default values? This action cannot be undone.')) {
|
||||
resetToDefaults();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Static config save button
|
||||
const saveStaticBtn = document.getElementById('config-save-static');
|
||||
if (saveStaticBtn) {
|
||||
saveStaticBtn.addEventListener('click', saveStaticConfig);
|
||||
}
|
||||
}
|
||||
|
||||
// Save live configuration changes
|
||||
function saveLiveConfiguration() {
|
||||
const liveInputs = document.querySelectorAll('.config-toggle, .config-number, .config-text');
|
||||
const updates = {};
|
||||
|
||||
liveInputs.forEach(input => {
|
||||
const varName = input.dataset.var;
|
||||
let value;
|
||||
|
||||
if (input.type === 'checkbox') {
|
||||
value = input.checked;
|
||||
} else if (input.type === 'number') {
|
||||
value = parseInt(input.value) || 0;
|
||||
} else {
|
||||
value = input.value;
|
||||
}
|
||||
|
||||
updates[varName] = value;
|
||||
});
|
||||
|
||||
console.log('Saving live configuration:', updates);
|
||||
|
||||
// Show status message
|
||||
const statusContainer = document.getElementById('config-status');
|
||||
if (statusContainer) {
|
||||
statusContainer.innerHTML = '<div class="alert alert-info"><i class="ri-loader-4-line"></i> Saving configuration...</div>';
|
||||
}
|
||||
|
||||
// Send to backend via fetch API
|
||||
fetch('/admin/api/config/update', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({ updates: updates })
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (statusContainer) {
|
||||
if (data.status === 'success') {
|
||||
statusContainer.innerHTML = '<div class="alert alert-success"><i class="ri-check-line"></i> Configuration saved successfully! Reloading page...</div>';
|
||||
|
||||
// Reload the page after a short delay
|
||||
setTimeout(() => {
|
||||
location.reload();
|
||||
}, 1000);
|
||||
} else {
|
||||
statusContainer.innerHTML = `<div class="alert alert-danger"><i class="ri-error-warning-line"></i> Error: ${data.message || 'Failed to save configuration'}</div>`;
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Save error:', error);
|
||||
if (statusContainer) {
|
||||
statusContainer.innerHTML = '<div class="alert alert-danger"><i class="ri-error-warning-line"></i> Error: Failed to save configuration</div>';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Reset all settings to defaults
|
||||
function resetToDefaults() {
|
||||
console.log('Resetting to defaults');
|
||||
|
||||
// Reset all form inputs
|
||||
document.querySelectorAll('.config-toggle, .config-number, .config-text').forEach(input => {
|
||||
if (input.type === 'checkbox') {
|
||||
input.checked = false;
|
||||
} else {
|
||||
input.value = '';
|
||||
}
|
||||
});
|
||||
|
||||
// Update badges
|
||||
Object.keys(window.CURRENT_CONFIG).forEach(varName => {
|
||||
updateConfigBadge(varName, null);
|
||||
});
|
||||
|
||||
// Show status message
|
||||
const statusContainer = document.getElementById('config-status');
|
||||
if (statusContainer) {
|
||||
statusContainer.innerHTML = '<div class="alert alert-warning"><i class="ri-restart-line"></i> Settings reset to defaults. Click "Save All Changes" to apply.</div>';
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize when DOM is ready
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
console.log('DOM loaded, initializing configuration interface');
|
||||
|
||||
// Initialize form values
|
||||
initializeConfigValues();
|
||||
|
||||
// Setup button handlers
|
||||
setupButtonHandlers();
|
||||
|
||||
// Set up event listeners for form changes
|
||||
document.addEventListener('change', (e) => {
|
||||
if (e.target.matches('[data-var]')) {
|
||||
handleConfigChange(e.target);
|
||||
}
|
||||
});
|
||||
|
||||
console.log('Configuration interface initialized - ready for API calls');
|
||||
});
|
||||
@@ -1,29 +1,921 @@
|
||||
{% import 'macro/accordion.html' as accordion %}
|
||||
|
||||
{{ accordion.header('Configuration variables', 'configuration', 'admin', icon='list-settings-line') }}
|
||||
<ul class="list-group list-group-flush">
|
||||
{% for entry in configuration %}
|
||||
<li class="list-group-item">
|
||||
<strong>{{ entry.name }}</strong>:
|
||||
{% if entry.value == none or entry.value == '' %}
|
||||
<span class="badge rounded-pill text-bg-secondary">Unset</span>
|
||||
{% elif entry.value == true %}
|
||||
<span class="badge rounded-pill text-bg-success">True</span>
|
||||
{% elif entry.value == false %}
|
||||
<span class="badge rounded-pill text-bg-danger">False</span>
|
||||
{% else %}
|
||||
{% if entry.is_secret() %}
|
||||
<span class="badge rounded-pill text-bg-success">Set</span>
|
||||
{% else %}
|
||||
<code>{{ entry.value }}</code>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
<span class="badge rounded-pill text-bg-light border">Env: {{ entry.env_name }}</span>
|
||||
{% if entry.extra_name %}<span class="badge rounded-pill text-bg-light border">Env: {{ entry.extra_name }}</span>{% endif %}
|
||||
{% if entry.is_changed() %}
|
||||
<span class="badge rounded-pill text-bg-warning">Changed</span>
|
||||
{% endif %}
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
<!-- Helper macro for config badges with truncation -->
|
||||
{% macro config_badges(var_name) %}
|
||||
{% set current_value = env_values[var_name] %}
|
||||
{% set default_value = config_defaults[var_name] %}
|
||||
{% set is_explicitly_set = env_explicit_values[var_name] %}
|
||||
|
||||
<!-- Value badge -->
|
||||
{% if not is_explicitly_set %}
|
||||
<!-- Not in .env file, using default -->
|
||||
<span class="badge rounded-pill text-bg-secondary ms-2" data-badge-var="{{ var_name }}" data-badge-type="value">Unset</span>
|
||||
<span class="badge rounded-pill text-bg-light text-dark ms-1" data-badge-var="{{ var_name }}" data-badge-type="default">Default Value</span>
|
||||
{% elif current_value is sameas true %}
|
||||
<span class="badge rounded-pill text-bg-success ms-2" data-badge-var="{{ var_name }}" data-badge-type="value">True</span>
|
||||
{% elif current_value is sameas false %}
|
||||
<span class="badge rounded-pill text-bg-danger ms-2" data-badge-var="{{ var_name }}" data-badge-type="value">False</span>
|
||||
{% elif current_value == default_value %}
|
||||
<!-- Explicitly set to default value -->
|
||||
<span class="badge rounded-pill text-bg-light text-dark ms-2" data-badge-var="{{ var_name }}" data-badge-type="value">Default: {{ default_value }}</span>
|
||||
{% else %}
|
||||
<!-- For text/number fields that have been changed, show "Default: X" -->
|
||||
<span class="badge rounded-pill text-bg-light text-dark ms-2" data-badge-var="{{ var_name }}" data-badge-type="value">Default: {{ default_value }}</span>
|
||||
{% endif %}
|
||||
|
||||
<!-- Changed badge -->
|
||||
{% if current_value != default_value %}
|
||||
<span class="badge rounded-pill text-bg-warning ms-1" data-badge-var="{{ var_name }}" data-badge-type="changed">Changed</span>
|
||||
{% endif %}
|
||||
{% endmacro %}
|
||||
|
||||
{{ accordion.header('Configuration Management', 'configuration-management', 'admin', icon='settings-4-line') }}
|
||||
<div class="p-3">
|
||||
<!-- Configuration Status -->
|
||||
<div id="config-status" class="mb-3">
|
||||
<!-- Status indicators -->
|
||||
</div>
|
||||
|
||||
<!-- Badge Legend -->
|
||||
<div class="alert alert-info mb-3">
|
||||
<h6 class="mb-2"><i class="ri-information-line"></i> Badge Legend</h6>
|
||||
<div class="row g-2">
|
||||
<div class="col-md-6">
|
||||
<small>
|
||||
<span class="badge rounded-pill text-bg-success">True</span> Boolean setting enabled<br>
|
||||
<span class="badge rounded-pill text-bg-danger">False</span> Boolean setting disabled<br>
|
||||
<span class="badge rounded-pill text-bg-primary">Set</span> Custom value configured
|
||||
</small>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<small>
|
||||
<span class="badge rounded-pill text-bg-secondary">Unset</span> Not in .env file<br>
|
||||
<span class="badge rounded-pill text-bg-light text-dark">Default Value</span> Using default value<br>
|
||||
<span class="badge rounded-pill text-bg-warning">Changed</span> Modified from default
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Sub-Drawers -->
|
||||
<div class="accordion" id="configuration-accordion">
|
||||
<!-- Live Settings Sub-Drawer -->
|
||||
<div class="accordion-item">
|
||||
<h2 class="accordion-header" id="live-settings-heading">
|
||||
<button class="accordion-button" type="button" data-bs-toggle="collapse" data-bs-target="#live-settings-collapse" aria-expanded="true" aria-controls="live-settings-collapse">
|
||||
<i class="ri-settings-4-line me-2"></i> Live Settings
|
||||
<span class="badge text-bg-success ms-2">Changes Applied On Save</span>
|
||||
</button>
|
||||
</h2>
|
||||
<div id="live-settings-collapse" class="accordion-collapse collapse show" aria-labelledby="live-settings-heading" data-bs-parent="#configuration-accordion">
|
||||
<div class="accordion-body">
|
||||
<!-- Action buttons -->
|
||||
<div class="d-flex gap-2 justify-content-end mb-4">
|
||||
<button id="config-save-all" class="btn btn-success">
|
||||
<i class="ri-save-line"></i> Save All Changes
|
||||
</button>
|
||||
<button id="config-refresh" class="btn btn-outline-secondary">
|
||||
<i class="ri-refresh-line"></i> Refresh
|
||||
</button>
|
||||
<button id="config-reset" class="btn btn-outline-secondary">
|
||||
<i class="ri-restart-line"></i> Reset to Defaults
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Live configuration controls -->
|
||||
|
||||
<!-- Menu Visibility -->
|
||||
<h6 class="fw-bold text-primary border-bottom pb-1 mb-3">Menu Visibility</h6>
|
||||
|
||||
<div class="row g-3">
|
||||
<div class="col-md-6">
|
||||
<div class="form-check form-switch">
|
||||
<input class="form-check-input config-toggle" type="checkbox" id="BK_HIDE_ADD_SET" data-var="BK_HIDE_ADD_SET">
|
||||
<label class="form-check-label" for="BK_HIDE_ADD_SET">
|
||||
BK_HIDE_ADD_SET {{ config_badges('BK_HIDE_ADD_SET') }}
|
||||
<div class="text-muted small">Hide the "Add Set" menu entry</div>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="form-check form-switch">
|
||||
<input class="form-check-input config-toggle" type="checkbox" id="BK_HIDE_ADD_BULK_SET" data-var="BK_HIDE_ADD_BULK_SET">
|
||||
<label class="form-check-label" for="BK_HIDE_ADD_BULK_SET">
|
||||
BK_HIDE_ADD_BULK_SET {{ config_badges('BK_HIDE_ADD_BULK_SET') }}
|
||||
<div class="text-muted small">Hide the "Add Bulk Set" menu entry</div>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="form-check form-switch">
|
||||
<input class="form-check-input config-toggle" type="checkbox" id="BK_HIDE_ADMIN" data-var="BK_HIDE_ADMIN">
|
||||
<label class="form-check-label" for="BK_HIDE_ADMIN">
|
||||
BK_HIDE_ADMIN {{ config_badges('BK_HIDE_ADMIN') }}
|
||||
<div class="text-muted small">Hide the "Admin" menu entry</div>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="form-check form-switch">
|
||||
<input class="form-check-input config-toggle" type="checkbox" id="BK_HIDE_ALL_INSTRUCTIONS" data-var="BK_HIDE_ALL_INSTRUCTIONS">
|
||||
<label class="form-check-label" for="BK_HIDE_ALL_INSTRUCTIONS">
|
||||
BK_HIDE_ALL_INSTRUCTIONS {{ config_badges('BK_HIDE_ALL_INSTRUCTIONS') }}
|
||||
<div class="text-muted small">Hide the "Instructions" menu entry</div>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="form-check form-switch">
|
||||
<input class="form-check-input config-toggle" type="checkbox" id="BK_HIDE_ALL_MINIFIGURES" data-var="BK_HIDE_ALL_MINIFIGURES">
|
||||
<label class="form-check-label" for="BK_HIDE_ALL_MINIFIGURES">
|
||||
BK_HIDE_ALL_MINIFIGURES {{ config_badges('BK_HIDE_ALL_MINIFIGURES') }}
|
||||
<div class="text-muted small">Hide the "Minifigures" menu entry</div>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="form-check form-switch">
|
||||
<input class="form-check-input config-toggle" type="checkbox" id="BK_HIDE_ALL_PARTS" data-var="BK_HIDE_ALL_PARTS">
|
||||
<label class="form-check-label" for="BK_HIDE_ALL_PARTS">
|
||||
BK_HIDE_ALL_PARTS {{ config_badges('BK_HIDE_ALL_PARTS') }}
|
||||
<div class="text-muted small">Hide the "Parts" menu entry</div>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<div class="form-check form-switch">
|
||||
<input class="form-check-input config-toggle" type="checkbox" id="BK_HIDE_ALL_PROBLEMS_PARTS" data-var="BK_HIDE_ALL_PROBLEMS_PARTS">
|
||||
<label class="form-check-label" for="BK_HIDE_ALL_PROBLEMS_PARTS">
|
||||
BK_HIDE_ALL_PROBLEMS_PARTS {{ config_badges('BK_HIDE_ALL_PROBLEMS_PARTS') }}
|
||||
<div class="text-muted small">Hide the "Problems" menu entry</div>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="form-check form-switch">
|
||||
<input class="form-check-input config-toggle" type="checkbox" id="BK_HIDE_ALL_SETS" data-var="BK_HIDE_ALL_SETS">
|
||||
<label class="form-check-label" for="BK_HIDE_ALL_SETS">
|
||||
BK_HIDE_ALL_SETS {{ config_badges('BK_HIDE_ALL_SETS') }}
|
||||
<div class="text-muted small">Hide the "Sets" menu entry</div>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="form-check form-switch">
|
||||
<input class="form-check-input config-toggle" type="checkbox" id="BK_HIDE_ALL_STORAGES" data-var="BK_HIDE_ALL_STORAGES">
|
||||
<label class="form-check-label" for="BK_HIDE_ALL_STORAGES">
|
||||
BK_HIDE_ALL_STORAGES {{ config_badges('BK_HIDE_ALL_STORAGES') }}
|
||||
<div class="text-muted small">Hide the "Storages" menu entry</div>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="form-check form-switch">
|
||||
<input class="form-check-input config-toggle" type="checkbox" id="BK_HIDE_STATISTICS" data-var="BK_HIDE_STATISTICS">
|
||||
<label class="form-check-label" for="BK_HIDE_STATISTICS">
|
||||
BK_HIDE_STATISTICS {{ config_badges('BK_HIDE_STATISTICS') }}
|
||||
<div class="text-muted small">Hide the "Statistics" menu entry</div>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="form-check form-switch">
|
||||
<input class="form-check-input config-toggle" type="checkbox" id="BK_HIDE_WISHES" data-var="BK_HIDE_WISHES">
|
||||
<label class="form-check-label" for="BK_HIDE_WISHES">
|
||||
BK_HIDE_WISHES {{ config_badges('BK_HIDE_WISHES') }}
|
||||
<div class="text-muted small">Hide the "Wishes" menu entry</div>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Table Display -->
|
||||
<h6 class="fw-bold text-primary border-bottom pb-1 mb-3 mt-4">Table Display</h6>
|
||||
|
||||
<div class="row g-3">
|
||||
<div class="col-md-6">
|
||||
<div class="form-check form-switch">
|
||||
<input class="form-check-input config-toggle" type="checkbox" id="BK_HIDE_SET_INSTRUCTIONS" data-var="BK_HIDE_SET_INSTRUCTIONS">
|
||||
<label class="form-check-label" for="BK_HIDE_SET_INSTRUCTIONS">
|
||||
BK_HIDE_SET_INSTRUCTIONS {{ config_badges('BK_HIDE_SET_INSTRUCTIONS') }}
|
||||
<div class="text-muted small">Hide instructions section in set details</div>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="form-check form-switch">
|
||||
<input class="form-check-input config-toggle" type="checkbox" id="BK_HIDE_TABLE_DAMAGED_PARTS" data-var="BK_HIDE_TABLE_DAMAGED_PARTS">
|
||||
<label class="form-check-label" for="BK_HIDE_TABLE_DAMAGED_PARTS">
|
||||
BK_HIDE_TABLE_DAMAGED_PARTS {{ config_badges('BK_HIDE_TABLE_DAMAGED_PARTS') }}
|
||||
<div class="text-muted small">Hide the "Damaged" column in parts tables</div>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="form-check form-switch">
|
||||
<input class="form-check-input config-toggle" type="checkbox" id="BK_HIDE_TABLE_MISSING_PARTS" data-var="BK_HIDE_TABLE_MISSING_PARTS">
|
||||
<label class="form-check-label" for="BK_HIDE_TABLE_MISSING_PARTS">
|
||||
BK_HIDE_TABLE_MISSING_PARTS {{ config_badges('BK_HIDE_TABLE_MISSING_PARTS') }}
|
||||
<div class="text-muted small">Hide the "Missing" column in parts tables</div>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="form-check form-switch">
|
||||
<input class="form-check-input config-toggle" type="checkbox" id="BK_HIDE_TABLE_CHECKED_PARTS" data-var="BK_HIDE_TABLE_CHECKED_PARTS">
|
||||
<label class="form-check-label" for="BK_HIDE_TABLE_CHECKED_PARTS">
|
||||
BK_HIDE_TABLE_CHECKED_PARTS {{ config_badges('BK_HIDE_TABLE_CHECKED_PARTS') }}
|
||||
<div class="text-muted small">Hide the "Checked" column in parts tables</div>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<div class="form-check form-switch">
|
||||
<input class="form-check-input config-toggle" type="checkbox" id="BK_SHOW_GRID_FILTERS" data-var="BK_SHOW_GRID_FILTERS">
|
||||
<label class="form-check-label" for="BK_SHOW_GRID_FILTERS">
|
||||
BK_SHOW_GRID_FILTERS {{ config_badges('BK_SHOW_GRID_FILTERS') }}
|
||||
<div class="text-muted small">Show filter controls on grid views by default</div>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="form-check form-switch">
|
||||
<input class="form-check-input config-toggle" type="checkbox" id="BK_SHOW_GRID_SORT" data-var="BK_SHOW_GRID_SORT">
|
||||
<label class="form-check-label" for="BK_SHOW_GRID_SORT">
|
||||
BK_SHOW_GRID_SORT {{ config_badges('BK_SHOW_GRID_SORT') }}
|
||||
<div class="text-muted small">Show sort options on grids by default</div>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="form-check form-switch">
|
||||
<input class="form-check-input config-toggle" type="checkbox" id="BK_INDEPENDENT_ACCORDIONS" data-var="BK_INDEPENDENT_ACCORDIONS">
|
||||
<label class="form-check-label" for="BK_INDEPENDENT_ACCORDIONS">
|
||||
BK_INDEPENDENT_ACCORDIONS {{ config_badges('BK_INDEPENDENT_ACCORDIONS') }}
|
||||
<div class="text-muted small">Make accordion sections independent (can open multiple)</div>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="form-check form-switch">
|
||||
<input class="form-check-input config-toggle" type="checkbox" id="BK_SETS_CONSOLIDATION" data-var="BK_SETS_CONSOLIDATION">
|
||||
<label class="form-check-label" for="BK_SETS_CONSOLIDATION">
|
||||
BK_SETS_CONSOLIDATION {{ config_badges('BK_SETS_CONSOLIDATION') }}
|
||||
<div class="text-muted small">Enable set consolidation/grouping functionality</div>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Pagination Settings -->
|
||||
<h6 class="fw-bold text-primary border-bottom pb-1 mb-3 mt-4">Pagination Settings</h6>
|
||||
|
||||
<!-- Sets and Parts (Top Row) -->
|
||||
<div class="row g-4 mb-4">
|
||||
<!-- Sets Column -->
|
||||
<div class="col-md-6">
|
||||
<h6 class="fw-bold text-secondary mb-3">Sets</h6>
|
||||
<div class="row g-3">
|
||||
<div class="col-12">
|
||||
<div class="form-check form-switch">
|
||||
<input class="form-check-input config-toggle" type="checkbox" id="BK_SETS_SERVER_SIDE_PAGINATION" data-var="BK_SETS_SERVER_SIDE_PAGINATION">
|
||||
<label class="form-check-label" for="BK_SETS_SERVER_SIDE_PAGINATION">
|
||||
BK_SETS_SERVER_SIDE_PAGINATION {{ config_badges('BK_SETS_SERVER_SIDE_PAGINATION') }}
|
||||
<div class="text-muted small">Enable/disable pagination for sets</div>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<label for="BK_SETS_PAGINATION_SIZE_DESKTOP" class="form-label">
|
||||
BK_SETS_PAGINATION_SIZE_DESKTOP {{ config_badges('BK_SETS_PAGINATION_SIZE_DESKTOP') }}
|
||||
<div class="text-muted small">Sets per page on desktop</div>
|
||||
</label>
|
||||
<input type="number" class="form-control config-number" id="BK_SETS_PAGINATION_SIZE_DESKTOP" data-var="BK_SETS_PAGINATION_SIZE_DESKTOP" min="1" max="100">
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<label for="BK_SETS_PAGINATION_SIZE_MOBILE" class="form-label">
|
||||
BK_SETS_PAGINATION_SIZE_MOBILE {{ config_badges('BK_SETS_PAGINATION_SIZE_MOBILE') }}
|
||||
<div class="text-muted small">Sets per page on mobile</div>
|
||||
</label>
|
||||
<input type="number" class="form-control config-number" id="BK_SETS_PAGINATION_SIZE_MOBILE" data-var="BK_SETS_PAGINATION_SIZE_MOBILE" min="1" max="50">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Parts Column -->
|
||||
<div class="col-md-6">
|
||||
<h6 class="fw-bold text-secondary mb-3">Parts</h6>
|
||||
<div class="row g-3">
|
||||
<div class="col-12">
|
||||
<div class="form-check form-switch">
|
||||
<input class="form-check-input config-toggle" type="checkbox" id="BK_PARTS_SERVER_SIDE_PAGINATION" data-var="BK_PARTS_SERVER_SIDE_PAGINATION">
|
||||
<label class="form-check-label" for="BK_PARTS_SERVER_SIDE_PAGINATION">
|
||||
BK_PARTS_SERVER_SIDE_PAGINATION {{ config_badges('BK_PARTS_SERVER_SIDE_PAGINATION') }}
|
||||
<div class="text-muted small">Enable/disable pagination for parts</div>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<label for="BK_PARTS_PAGINATION_SIZE_DESKTOP" class="form-label">
|
||||
BK_PARTS_PAGINATION_SIZE_DESKTOP {{ config_badges('BK_PARTS_PAGINATION_SIZE_DESKTOP') }}
|
||||
<div class="text-muted small">Parts per page on desktop</div>
|
||||
</label>
|
||||
<input type="number" class="form-control config-number" id="BK_PARTS_PAGINATION_SIZE_DESKTOP" data-var="BK_PARTS_PAGINATION_SIZE_DESKTOP" min="1" max="100">
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<label for="BK_PARTS_PAGINATION_SIZE_MOBILE" class="form-label">
|
||||
BK_PARTS_PAGINATION_SIZE_MOBILE {{ config_badges('BK_PARTS_PAGINATION_SIZE_MOBILE') }}
|
||||
<div class="text-muted small">Parts per page on mobile</div>
|
||||
</label>
|
||||
<input type="number" class="form-control config-number" id="BK_PARTS_PAGINATION_SIZE_MOBILE" data-var="BK_PARTS_PAGINATION_SIZE_MOBILE" min="1" max="50">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Minifigures and Problems (Bottom Row) -->
|
||||
<div class="row g-4 mb-4">
|
||||
<!-- Minifigures Column -->
|
||||
<div class="col-md-6">
|
||||
<h6 class="fw-bold text-secondary mb-3">Minifigures</h6>
|
||||
<div class="row g-3">
|
||||
<div class="col-12">
|
||||
<div class="form-check form-switch">
|
||||
<input class="form-check-input config-toggle" type="checkbox" id="BK_MINIFIGURES_SERVER_SIDE_PAGINATION" data-var="BK_MINIFIGURES_SERVER_SIDE_PAGINATION">
|
||||
<label class="form-check-label" for="BK_MINIFIGURES_SERVER_SIDE_PAGINATION">
|
||||
BK_MINIFIGURES_SERVER_SIDE_PAGINATION {{ config_badges('BK_MINIFIGURES_SERVER_SIDE_PAGINATION') }}
|
||||
<div class="text-muted small">Enable/disable pagination for minifigures</div>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<label for="BK_MINIFIGURES_PAGINATION_SIZE_DESKTOP" class="form-label">
|
||||
BK_MINIFIGURES_PAGINATION_SIZE_DESKTOP {{ config_badges('BK_MINIFIGURES_PAGINATION_SIZE_DESKTOP') }}
|
||||
<div class="text-muted small">Minifigures per page on desktop</div>
|
||||
</label>
|
||||
<input type="number" class="form-control config-number" id="BK_MINIFIGURES_PAGINATION_SIZE_DESKTOP" data-var="BK_MINIFIGURES_PAGINATION_SIZE_DESKTOP" min="1" max="100">
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<label for="BK_MINIFIGURES_PAGINATION_SIZE_MOBILE" class="form-label">
|
||||
BK_MINIFIGURES_PAGINATION_SIZE_MOBILE {{ config_badges('BK_MINIFIGURES_PAGINATION_SIZE_MOBILE') }}
|
||||
<div class="text-muted small">Minifigures per page on mobile</div>
|
||||
</label>
|
||||
<input type="number" class="form-control config-number" id="BK_MINIFIGURES_PAGINATION_SIZE_MOBILE" data-var="BK_MINIFIGURES_PAGINATION_SIZE_MOBILE" min="1" max="50">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Problems Column -->
|
||||
<div class="col-md-6">
|
||||
<h6 class="fw-bold text-secondary mb-3">Problems</h6>
|
||||
<div class="row g-3">
|
||||
<div class="col-12">
|
||||
<div class="form-check form-switch">
|
||||
<input class="form-check-input config-toggle" type="checkbox" id="BK_PROBLEMS_SERVER_SIDE_PAGINATION" data-var="BK_PROBLEMS_SERVER_SIDE_PAGINATION">
|
||||
<label class="form-check-label" for="BK_PROBLEMS_SERVER_SIDE_PAGINATION">
|
||||
BK_PROBLEMS_SERVER_SIDE_PAGINATION {{ config_badges('BK_PROBLEMS_SERVER_SIDE_PAGINATION') }}
|
||||
<div class="text-muted small">Enable/disable pagination for problems</div>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<label for="BK_PROBLEMS_PAGINATION_SIZE_DESKTOP" class="form-label">
|
||||
BK_PROBLEMS_PAGINATION_SIZE_DESKTOP {{ config_badges('BK_PROBLEMS_PAGINATION_SIZE_DESKTOP') }}
|
||||
<div class="text-muted small">Problem parts per page on desktop</div>
|
||||
</label>
|
||||
<input type="number" class="form-control config-number" id="BK_PROBLEMS_PAGINATION_SIZE_DESKTOP" data-var="BK_PROBLEMS_PAGINATION_SIZE_DESKTOP" min="1" max="100">
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<label for="BK_PROBLEMS_PAGINATION_SIZE_MOBILE" class="form-label">
|
||||
BK_PROBLEMS_PAGINATION_SIZE_MOBILE {{ config_badges('BK_PROBLEMS_PAGINATION_SIZE_MOBILE') }}
|
||||
<div class="text-muted small">Problem parts per page on mobile</div>
|
||||
</label>
|
||||
<input type="number" class="form-control config-number" id="BK_PROBLEMS_PAGINATION_SIZE_MOBILE" data-var="BK_PROBLEMS_PAGINATION_SIZE_MOBILE" min="1" max="50">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Client side pagination -->
|
||||
<h6 class="fw-bold text-secondary mb-3">Client side pagination</h6>
|
||||
<div class="row g-3">
|
||||
<div class="col-md-6">
|
||||
<label for="BK_DEFAULT_TABLE_PER_PAGE" class="form-label">
|
||||
BK_DEFAULT_TABLE_PER_PAGE {{ config_badges('BK_DEFAULT_TABLE_PER_PAGE') }}
|
||||
<div class="text-muted small">Default number of items per page in tables</div>
|
||||
</label>
|
||||
<input type="number" class="form-control config-number" id="BK_DEFAULT_TABLE_PER_PAGE" data-var="BK_DEFAULT_TABLE_PER_PAGE" min="1" max="500">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Features & External Services -->
|
||||
<h6 class="fw-bold text-primary border-bottom pb-1 mb-3 mt-4">Features & External Services</h6>
|
||||
|
||||
<div class="row g-3">
|
||||
<div class="col-md-6">
|
||||
<div class="form-check form-switch">
|
||||
<input class="form-check-input config-toggle" type="checkbox" id="BK_RANDOM" data-var="BK_RANDOM">
|
||||
<label class="form-check-label" for="BK_RANDOM">
|
||||
BK_RANDOM {{ config_badges('BK_RANDOM') }}
|
||||
<div class="text-muted small">Shuffle the lists on the front page</div>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="form-check form-switch">
|
||||
<input class="form-check-input config-toggle" type="checkbox" id="BK_BRICKLINK_LINKS" data-var="BK_BRICKLINK_LINKS">
|
||||
<label class="form-check-label" for="BK_BRICKLINK_LINKS">
|
||||
BK_BRICKLINK_LINKS {{ config_badges('BK_BRICKLINK_LINKS') }}
|
||||
<div class="text-muted small">Display BrickLink links wherever applicable</div>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="form-check form-switch">
|
||||
<input class="form-check-input config-toggle" type="checkbox" id="BK_REBRICKABLE_LINKS" data-var="BK_REBRICKABLE_LINKS">
|
||||
<label class="form-check-label" for="BK_REBRICKABLE_LINKS">
|
||||
BK_REBRICKABLE_LINKS {{ config_badges('BK_REBRICKABLE_LINKS') }}
|
||||
<div class="text-muted small">Display Rebrickable links wherever applicable</div>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="form-check form-switch">
|
||||
<input class="form-check-input config-toggle" type="checkbox" id="BK_SKIP_SPARE_PARTS" data-var="BK_SKIP_SPARE_PARTS">
|
||||
<label class="form-check-label" for="BK_SKIP_SPARE_PARTS">
|
||||
BK_SKIP_SPARE_PARTS {{ config_badges('BK_SKIP_SPARE_PARTS') }}
|
||||
<div class="text-muted small">Skip spare parts when importing sets</div>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<div class="form-check form-switch">
|
||||
<input class="form-check-input config-toggle" type="checkbox" id="BK_USE_REMOTE_IMAGES" data-var="BK_USE_REMOTE_IMAGES">
|
||||
<label class="form-check-label" for="BK_USE_REMOTE_IMAGES">
|
||||
BK_USE_REMOTE_IMAGES {{ config_badges('BK_USE_REMOTE_IMAGES') }}
|
||||
<div class="text-muted small">Use remote images from Rebrickable CDN instead of local storage</div>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="form-check form-switch">
|
||||
<input class="form-check-input config-toggle" type="checkbox" id="BK_STATISTICS_SHOW_CHARTS" data-var="BK_STATISTICS_SHOW_CHARTS">
|
||||
<label class="form-check-label" for="BK_STATISTICS_SHOW_CHARTS">
|
||||
BK_STATISTICS_SHOW_CHARTS {{ config_badges('BK_STATISTICS_SHOW_CHARTS') }}
|
||||
<div class="text-muted small">Show collection growth charts on statistics page</div>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="form-check form-switch">
|
||||
<input class="form-check-input config-toggle" type="checkbox" id="BK_STATISTICS_DEFAULT_EXPANDED" data-var="BK_STATISTICS_DEFAULT_EXPANDED">
|
||||
<label class="form-check-label" for="BK_STATISTICS_DEFAULT_EXPANDED">
|
||||
BK_STATISTICS_DEFAULT_EXPANDED {{ config_badges('BK_STATISTICS_DEFAULT_EXPANDED') }}
|
||||
<div class="text-muted small">Expand all statistics sections by default</div>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Advanced Settings -->
|
||||
<h6 class="fw-bold text-primary border-bottom pb-1 mb-3 mt-4">Advanced Settings</h6>
|
||||
|
||||
<div class="row g-3">
|
||||
<div class="col-md-6">
|
||||
<label for="BK_PEERON_DOWNLOAD_DELAY" class="form-label">
|
||||
BK_PEERON_DOWNLOAD_DELAY {{ config_badges('BK_PEERON_DOWNLOAD_DELAY') }}
|
||||
<div class="text-muted small">Delay between Peeron downloads in milliseconds</div>
|
||||
</label>
|
||||
<input type="number" class="form-control config-number" id="BK_PEERON_DOWNLOAD_DELAY" data-var="BK_PEERON_DOWNLOAD_DELAY" min="0" max="10000">
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<label for="BK_PEERON_MIN_IMAGE_SIZE" class="form-label">
|
||||
BK_PEERON_MIN_IMAGE_SIZE {{ config_badges('BK_PEERON_MIN_IMAGE_SIZE') }}
|
||||
<div class="text-muted small">Minimum valid image size in bytes</div>
|
||||
</label>
|
||||
<input type="number" class="form-control config-number" id="BK_PEERON_MIN_IMAGE_SIZE" data-var="BK_PEERON_MIN_IMAGE_SIZE" min="1000" max="1000000">
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<label for="BK_REBRICKABLE_PAGE_SIZE" class="form-label">
|
||||
BK_REBRICKABLE_PAGE_SIZE {{ config_badges('BK_REBRICKABLE_PAGE_SIZE') }}
|
||||
<div class="text-muted small">Number of items per page for Rebrickable API requests</div>
|
||||
</label>
|
||||
<input type="number" class="form-control config-number" id="BK_REBRICKABLE_PAGE_SIZE" data-var="BK_REBRICKABLE_PAGE_SIZE" min="100" max="5000">
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<label for="BK_ADMIN_DEFAULT_EXPANDED_SECTIONS" class="form-label">
|
||||
BK_ADMIN_DEFAULT_EXPANDED_SECTIONS {{ config_badges('BK_ADMIN_DEFAULT_EXPANDED_SECTIONS') }}
|
||||
<div class="text-muted small">Admin sections to expand by default (comma-separated)</div>
|
||||
</label>
|
||||
<input type="text" class="form-control config-text" id="BK_ADMIN_DEFAULT_EXPANDED_SECTIONS" data-var="BK_ADMIN_DEFAULT_EXPANDED_SECTIONS">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Default Ordering & Formatting -->
|
||||
<h6 class="fw-bold text-primary border-bottom pb-1 mb-3 mt-4">Default Ordering & Formatting</h6>
|
||||
|
||||
<div class="row g-3">
|
||||
<div class="col-12">
|
||||
<label for="BK_INSTRUCTIONS_ALLOWED_EXTENSIONS" class="form-label">
|
||||
BK_INSTRUCTIONS_ALLOWED_EXTENSIONS {{ config_badges('BK_INSTRUCTIONS_ALLOWED_EXTENSIONS') }}
|
||||
<div class="text-muted small">Allowed file extensions for instructions (comma-separated)</div>
|
||||
</label>
|
||||
<input type="text" class="form-control config-text" id="BK_INSTRUCTIONS_ALLOWED_EXTENSIONS" data-var="BK_INSTRUCTIONS_ALLOWED_EXTENSIONS">
|
||||
</div>
|
||||
|
||||
<div class="col-12">
|
||||
<label for="BK_MINIFIGURES_DEFAULT_ORDER" class="form-label">
|
||||
BK_MINIFIGURES_DEFAULT_ORDER {{ config_badges('BK_MINIFIGURES_DEFAULT_ORDER') }}
|
||||
<div class="text-muted small">SQL ORDER BY clause for minifigures listing</div>
|
||||
</label>
|
||||
<input type="text" class="form-control config-text" id="BK_MINIFIGURES_DEFAULT_ORDER" data-var="BK_MINIFIGURES_DEFAULT_ORDER">
|
||||
</div>
|
||||
|
||||
<div class="col-12">
|
||||
<label for="BK_PARTS_DEFAULT_ORDER" class="form-label">
|
||||
BK_PARTS_DEFAULT_ORDER {{ config_badges('BK_PARTS_DEFAULT_ORDER') }}
|
||||
<div class="text-muted small">SQL ORDER BY clause for parts listing</div>
|
||||
</label>
|
||||
<input type="text" class="form-control config-text" id="BK_PARTS_DEFAULT_ORDER" data-var="BK_PARTS_DEFAULT_ORDER">
|
||||
</div>
|
||||
|
||||
<div class="col-12">
|
||||
<label for="BK_SETS_DEFAULT_ORDER" class="form-label">
|
||||
BK_SETS_DEFAULT_ORDER {{ config_badges('BK_SETS_DEFAULT_ORDER') }}
|
||||
<div class="text-muted small">SQL ORDER BY clause for sets listing</div>
|
||||
</label>
|
||||
<input type="text" class="form-control config-text" id="BK_SETS_DEFAULT_ORDER" data-var="BK_SETS_DEFAULT_ORDER">
|
||||
</div>
|
||||
|
||||
|
||||
<div class="col-12">
|
||||
<label for="BK_PURCHASE_LOCATION_DEFAULT_ORDER" class="form-label">
|
||||
BK_PURCHASE_LOCATION_DEFAULT_ORDER {{ config_badges('BK_PURCHASE_LOCATION_DEFAULT_ORDER') }}
|
||||
<div class="text-muted small">SQL ORDER BY clause for purchase locations</div>
|
||||
</label>
|
||||
<input type="text" class="form-control config-text" id="BK_PURCHASE_LOCATION_DEFAULT_ORDER" data-var="BK_PURCHASE_LOCATION_DEFAULT_ORDER">
|
||||
</div>
|
||||
|
||||
<div class="col-12">
|
||||
<label for="BK_STORAGE_DEFAULT_ORDER" class="form-label">
|
||||
BK_STORAGE_DEFAULT_ORDER {{ config_badges('BK_STORAGE_DEFAULT_ORDER') }}
|
||||
<div class="text-muted small">SQL ORDER BY clause for storage locations</div>
|
||||
</label>
|
||||
<input type="text" class="form-control config-text" id="BK_STORAGE_DEFAULT_ORDER" data-var="BK_STORAGE_DEFAULT_ORDER">
|
||||
</div>
|
||||
|
||||
<div class="col-12">
|
||||
<label for="BK_WISHES_DEFAULT_ORDER" class="form-label">
|
||||
BK_WISHES_DEFAULT_ORDER {{ config_badges('BK_WISHES_DEFAULT_ORDER') }}
|
||||
<div class="text-muted small">SQL ORDER BY clause for wishes listing</div>
|
||||
</label>
|
||||
<input type="text" class="form-control config-text" id="BK_WISHES_DEFAULT_ORDER" data-var="BK_WISHES_DEFAULT_ORDER">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- URL Patterns & Links -->
|
||||
<h6 class="fw-bold text-primary border-bottom pb-1 mb-3 mt-4">URL Patterns & Links</h6>
|
||||
|
||||
<div class="row g-3">
|
||||
<div class="col-12">
|
||||
<label for="BK_BRICKLINK_LINK_PART_PATTERN" class="form-label">
|
||||
BK_BRICKLINK_LINK_PART_PATTERN {{ config_badges('BK_BRICKLINK_LINK_PART_PATTERN') }}
|
||||
<div class="text-muted small">Pattern for BrickLink part links (supports {part} and {color})</div>
|
||||
</label>
|
||||
<input type="text" class="form-control config-text" id="BK_BRICKLINK_LINK_PART_PATTERN" data-var="BK_BRICKLINK_LINK_PART_PATTERN">
|
||||
</div>
|
||||
|
||||
<div class="col-12">
|
||||
<label for="BK_REBRICKABLE_LINK_MINIFIGURE_PATTERN" class="form-label">
|
||||
BK_REBRICKABLE_LINK_MINIFIGURE_PATTERN {{ config_badges('BK_REBRICKABLE_LINK_MINIFIGURE_PATTERN') }}
|
||||
<div class="text-muted small">Pattern for Rebrickable minifigure links (supports {figure})</div>
|
||||
</label>
|
||||
<input type="text" class="form-control config-text" id="BK_REBRICKABLE_LINK_MINIFIGURE_PATTERN" data-var="BK_REBRICKABLE_LINK_MINIFIGURE_PATTERN">
|
||||
</div>
|
||||
|
||||
<div class="col-12">
|
||||
<label for="BK_REBRICKABLE_LINK_PART_PATTERN" class="form-label">
|
||||
BK_REBRICKABLE_LINK_PART_PATTERN {{ config_badges('BK_REBRICKABLE_LINK_PART_PATTERN') }}
|
||||
<div class="text-muted small">Pattern for Rebrickable part links (supports {part} and {color})</div>
|
||||
</label>
|
||||
<input type="text" class="form-control config-text" id="BK_REBRICKABLE_LINK_PART_PATTERN" data-var="BK_REBRICKABLE_LINK_PART_PATTERN">
|
||||
</div>
|
||||
|
||||
<div class="col-12">
|
||||
<label for="BK_REBRICKABLE_LINK_INSTRUCTIONS_PATTERN" class="form-label">
|
||||
BK_REBRICKABLE_LINK_INSTRUCTIONS_PATTERN {{ config_badges('BK_REBRICKABLE_LINK_INSTRUCTIONS_PATTERN') }}
|
||||
<div class="text-muted small">Pattern for Rebrickable instruction links (supports {path})</div>
|
||||
</label>
|
||||
<input type="text" class="form-control config-text" id="BK_REBRICKABLE_LINK_INSTRUCTIONS_PATTERN" data-var="BK_REBRICKABLE_LINK_INSTRUCTIONS_PATTERN">
|
||||
</div>
|
||||
|
||||
<div class="col-12">
|
||||
<label for="BK_PEERON_INSTRUCTION_PATTERN" class="form-label">
|
||||
BK_PEERON_INSTRUCTION_PATTERN {{ config_badges('BK_PEERON_INSTRUCTION_PATTERN') }}
|
||||
<div class="text-muted small">Pattern for Peeron instruction URLs (supports {set_number} and {version_number})</div>
|
||||
</label>
|
||||
<input type="text" class="form-control config-text" id="BK_PEERON_INSTRUCTION_PATTERN" data-var="BK_PEERON_INSTRUCTION_PATTERN">
|
||||
</div>
|
||||
|
||||
<div class="col-12">
|
||||
<label for="BK_PEERON_SCAN_PATTERN" class="form-label">
|
||||
BK_PEERON_SCAN_PATTERN {{ config_badges('BK_PEERON_SCAN_PATTERN') }}
|
||||
<div class="text-muted small">Pattern for Peeron scan URLs (supports {set_number} and {version_number})</div>
|
||||
</label>
|
||||
<input type="text" class="form-control config-text" id="BK_PEERON_SCAN_PATTERN" data-var="BK_PEERON_SCAN_PATTERN">
|
||||
</div>
|
||||
|
||||
<div class="col-12">
|
||||
<label for="BK_PEERON_THUMBNAIL_PATTERN" class="form-label">
|
||||
BK_PEERON_THUMBNAIL_PATTERN {{ config_badges('BK_PEERON_THUMBNAIL_PATTERN') }}
|
||||
<div class="text-muted small">Pattern for Peeron thumbnail URLs (supports {set_number} and {version_number})</div>
|
||||
</label>
|
||||
<input type="text" class="form-control config-text" id="BK_PEERON_THUMBNAIL_PATTERN" data-var="BK_PEERON_THUMBNAIL_PATTERN">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Images & Resources -->
|
||||
<h6 class="fw-bold text-primary border-bottom pb-1 mb-3 mt-4">Images & Resources</h6>
|
||||
|
||||
<div class="row g-3">
|
||||
<div class="col-12">
|
||||
<label for="BK_REBRICKABLE_IMAGE_NIL" class="form-label">
|
||||
BK_REBRICKABLE_IMAGE_NIL {{ config_badges('BK_REBRICKABLE_IMAGE_NIL') }}
|
||||
<div class="text-muted small">URL for missing image placeholder</div>
|
||||
</label>
|
||||
<input type="text" class="form-control config-text" id="BK_REBRICKABLE_IMAGE_NIL" data-var="BK_REBRICKABLE_IMAGE_NIL">
|
||||
</div>
|
||||
|
||||
<div class="col-12">
|
||||
<label for="BK_REBRICKABLE_IMAGE_NIL_MINIFIGURE" class="form-label">
|
||||
BK_REBRICKABLE_IMAGE_NIL_MINIFIGURE {{ config_badges('BK_REBRICKABLE_IMAGE_NIL_MINIFIGURE') }}
|
||||
<div class="text-muted small">URL for missing minifigure image placeholder</div>
|
||||
</label>
|
||||
<input type="text" class="form-control config-text" id="BK_REBRICKABLE_IMAGE_NIL_MINIFIGURE" data-var="BK_REBRICKABLE_IMAGE_NIL_MINIFIGURE">
|
||||
</div>
|
||||
|
||||
<div class="col-12">
|
||||
<label for="BK_RETIRED_SETS_FILE_URL" class="form-label">
|
||||
BK_RETIRED_SETS_FILE_URL {{ config_badges('BK_RETIRED_SETS_FILE_URL') }}
|
||||
<div class="text-muted small">URL to the retired sets CSV file</div>
|
||||
</label>
|
||||
<input type="text" class="form-control config-text" id="BK_RETIRED_SETS_FILE_URL" data-var="BK_RETIRED_SETS_FILE_URL">
|
||||
</div>
|
||||
|
||||
<div class="col-12">
|
||||
<label for="BK_RETIRED_SETS_PATH" class="form-label">
|
||||
BK_RETIRED_SETS_PATH {{ config_badges('BK_RETIRED_SETS_PATH') }}
|
||||
<div class="text-muted small">Local path to store retired sets CSV</div>
|
||||
</label>
|
||||
<input type="text" class="form-control config-text" id="BK_RETIRED_SETS_PATH" data-var="BK_RETIRED_SETS_PATH">
|
||||
</div>
|
||||
|
||||
<div class="col-12">
|
||||
<label for="BK_THEMES_FILE_URL" class="form-label">
|
||||
BK_THEMES_FILE_URL {{ config_badges('BK_THEMES_FILE_URL') }}
|
||||
<div class="text-muted small">URL to the Rebrickable themes CSV file</div>
|
||||
</label>
|
||||
<input type="text" class="form-control config-text" id="BK_THEMES_FILE_URL" data-var="BK_THEMES_FILE_URL">
|
||||
</div>
|
||||
|
||||
<div class="col-12">
|
||||
<label for="BK_THEMES_PATH" class="form-label">
|
||||
BK_THEMES_PATH {{ config_badges('BK_THEMES_PATH') }}
|
||||
<div class="text-muted small">Local path to store themes CSV</div>
|
||||
</label>
|
||||
<input type="text" class="form-control config-text" id="BK_THEMES_PATH" data-var="BK_THEMES_PATH">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Static Settings Sub-Drawer -->
|
||||
<div class="accordion-item">
|
||||
<h2 class="accordion-header" id="static-settings-heading">
|
||||
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#static-settings-collapse" aria-expanded="false" aria-controls="static-settings-collapse">
|
||||
<i class="ri-database-2-line me-2"></i> Static Settings
|
||||
<span class="badge text-bg-warning ms-2">Requires Restart</span>
|
||||
</button>
|
||||
</h2>
|
||||
<div id="static-settings-collapse" class="accordion-collapse collapse" aria-labelledby="static-settings-heading" data-bs-parent="#configuration-accordion">
|
||||
<div class="accordion-body">
|
||||
<!-- Static configuration with editable fields -->
|
||||
|
||||
<div class="alert alert-warning">
|
||||
<h6><i class="ri-warning-line"></i> Restart Required</h6>
|
||||
These settings require an application restart to take effect. Values can be edited here and will be saved to the .env file.
|
||||
</div>
|
||||
|
||||
<!-- Authentication -->
|
||||
<h6 class="fw-bold text-secondary border-bottom pb-1 mb-3">Authentication & Security</h6>
|
||||
<div class="row g-3">
|
||||
<div class="col-md-6">
|
||||
<label for="static-BK_AUTHENTICATION_PASSWORD" class="form-label">
|
||||
BK_AUTHENTICATION_PASSWORD {{ config_badges('BK_AUTHENTICATION_PASSWORD') }}
|
||||
<div class="text-muted small">Password for authentication system</div>
|
||||
</label>
|
||||
<input type="password" class="form-control config-static" id="static-BK_AUTHENTICATION_PASSWORD" data-var="BK_AUTHENTICATION_PASSWORD">
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label for="static-BK_AUTHENTICATION_KEY" class="form-label">
|
||||
BK_AUTHENTICATION_KEY {{ config_badges('BK_AUTHENTICATION_KEY') }}
|
||||
<div class="text-muted small">Secret key for session signing</div>
|
||||
</label>
|
||||
<input type="password" class="form-control config-static" id="static-BK_AUTHENTICATION_KEY" data-var="BK_AUTHENTICATION_KEY">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Server Configuration -->
|
||||
<h6 class="fw-bold text-secondary border-bottom pb-1 mb-3 mt-4">Server Configuration</h6>
|
||||
<div class="row g-3">
|
||||
<div class="col-md-6">
|
||||
<label for="static-BK_HOST" class="form-label">
|
||||
BK_HOST {{ config_badges('BK_HOST') }}
|
||||
<div class="text-muted small">Server host address to bind to</div>
|
||||
</label>
|
||||
<input type="text" class="form-control config-static" id="static-BK_HOST" data-var="BK_HOST">
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label for="static-BK_PORT" class="form-label">
|
||||
BK_PORT {{ config_badges('BK_PORT') }}
|
||||
<div class="text-muted small">Port number for the web server</div>
|
||||
</label>
|
||||
<input type="number" class="form-control config-static" id="static-BK_PORT" data-var="BK_PORT" min="1" max="65535">
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="form-check form-switch">
|
||||
<input class="form-check-input config-static-toggle" type="checkbox" id="static-BK_DEBUG" data-var="BK_DEBUG">
|
||||
<label class="form-check-label" for="static-BK_DEBUG">
|
||||
BK_DEBUG {{ config_badges('BK_DEBUG') }}
|
||||
<div class="text-muted small">Enable debug mode</div>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label for="static-BK_DOMAIN_NAME" class="form-label">
|
||||
BK_DOMAIN_NAME {{ config_badges('BK_DOMAIN_NAME') }}
|
||||
<div class="text-muted small">Domain name for CORS configuration</div>
|
||||
</label>
|
||||
<input type="text" class="form-control config-static" id="static-BK_DOMAIN_NAME" data-var="BK_DOMAIN_NAME">
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label for="static-BK_TIMEZONE" class="form-label">
|
||||
BK_TIMEZONE {{ config_badges('BK_TIMEZONE') }}
|
||||
<div class="text-muted small">Application timezone</div>
|
||||
</label>
|
||||
<input type="text" class="form-control config-static" id="static-BK_TIMEZONE" data-var="BK_TIMEZONE">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Database & Storage -->
|
||||
<h6 class="fw-bold text-secondary border-bottom pb-1 mb-3 mt-4">Database & Storage</h6>
|
||||
<div class="row g-3">
|
||||
<div class="col-md-6">
|
||||
<label for="static-BK_DATABASE_PATH" class="form-label">
|
||||
BK_DATABASE_PATH {{ config_badges('BK_DATABASE_PATH') }}
|
||||
<div class="text-muted small">Path to the SQLite database file</div>
|
||||
</label>
|
||||
<input type="text" class="form-control config-static" id="static-BK_DATABASE_PATH" data-var="BK_DATABASE_PATH">
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label for="static-BK_INSTRUCTIONS_FOLDER" class="form-label">
|
||||
BK_INSTRUCTIONS_FOLDER {{ config_badges('BK_INSTRUCTIONS_FOLDER') }}
|
||||
<div class="text-muted small">Folder for instruction files</div>
|
||||
</label>
|
||||
<input type="text" class="form-control config-static" id="static-BK_INSTRUCTIONS_FOLDER" data-var="BK_INSTRUCTIONS_FOLDER">
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label for="static-BK_PARTS_FOLDER" class="form-label">
|
||||
BK_PARTS_FOLDER {{ config_badges('BK_PARTS_FOLDER') }}
|
||||
<div class="text-muted small">Folder for part images</div>
|
||||
</label>
|
||||
<input type="text" class="form-control config-static" id="static-BK_PARTS_FOLDER" data-var="BK_PARTS_FOLDER">
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label for="static-BK_SETS_FOLDER" class="form-label">
|
||||
BK_SETS_FOLDER {{ config_badges('BK_SETS_FOLDER') }}
|
||||
<div class="text-muted small">Folder for set images</div>
|
||||
</label>
|
||||
<input type="text" class="form-control config-static" id="static-BK_SETS_FOLDER" data-var="BK_SETS_FOLDER">
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label for="static-BK_MINIFIGURES_FOLDER" class="form-label">
|
||||
BK_MINIFIGURES_FOLDER {{ config_badges('BK_MINIFIGURES_FOLDER') }}
|
||||
<div class="text-muted small">Folder for minifigure images</div>
|
||||
</label>
|
||||
<input type="text" class="form-control config-static" id="static-BK_MINIFIGURES_FOLDER" data-var="BK_MINIFIGURES_FOLDER">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- API Configuration -->
|
||||
<h6 class="fw-bold text-secondary border-bottom pb-1 mb-3 mt-4">API Configuration</h6>
|
||||
<div class="row g-3">
|
||||
<div class="col-md-6">
|
||||
<label for="static-BK_REBRICKABLE_API_KEY" class="form-label">
|
||||
BK_REBRICKABLE_API_KEY {{ config_badges('BK_REBRICKABLE_API_KEY') }}
|
||||
<div class="text-muted small">API key for Rebrickable integration</div>
|
||||
</label>
|
||||
<input type="password" class="form-control config-static" id="static-BK_REBRICKABLE_API_KEY" data-var="BK_REBRICKABLE_API_KEY">
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label for="static-BK_USER_AGENT" class="form-label">
|
||||
BK_USER_AGENT {{ config_badges('BK_USER_AGENT') }}
|
||||
<div class="text-muted small">User agent string for HTTP requests</div>
|
||||
</label>
|
||||
<input type="text" class="form-control config-static" id="static-BK_USER_AGENT" data-var="BK_USER_AGENT">
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label for="static-BK_REBRICKABLE_USER_AGENT" class="form-label">
|
||||
BK_REBRICKABLE_USER_AGENT {{ config_badges('BK_REBRICKABLE_USER_AGENT') }}
|
||||
<div class="text-muted small">User agent for Rebrickable API requests</div>
|
||||
</label>
|
||||
<input type="text" class="form-control config-static" id="static-BK_REBRICKABLE_USER_AGENT" data-var="BK_REBRICKABLE_USER_AGENT">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Date & Currency Formats -->
|
||||
<h6 class="fw-bold text-secondary border-bottom pb-1 mb-3 mt-4">Date & Currency Formats</h6>
|
||||
<div class="row g-3">
|
||||
<div class="col-md-6">
|
||||
<label for="static-BK_DATABASE_TIMESTAMP_FORMAT" class="form-label">
|
||||
BK_DATABASE_TIMESTAMP_FORMAT {{ config_badges('BK_DATABASE_TIMESTAMP_FORMAT') }}
|
||||
<div class="text-muted small">Database timestamp format</div>
|
||||
</label>
|
||||
<input type="text" class="form-control config-static" id="static-BK_DATABASE_TIMESTAMP_FORMAT" data-var="BK_DATABASE_TIMESTAMP_FORMAT">
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label for="static-BK_FILE_DATETIME_FORMAT" class="form-label">
|
||||
BK_FILE_DATETIME_FORMAT {{ config_badges('BK_FILE_DATETIME_FORMAT') }}
|
||||
<div class="text-muted small">File datetime format</div>
|
||||
</label>
|
||||
<input type="text" class="form-control config-static" id="static-BK_FILE_DATETIME_FORMAT" data-var="BK_FILE_DATETIME_FORMAT">
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label for="static-BK_PURCHASE_DATE_FORMAT" class="form-label">
|
||||
BK_PURCHASE_DATE_FORMAT {{ config_badges('BK_PURCHASE_DATE_FORMAT') }}
|
||||
<div class="text-muted small">Purchase date format</div>
|
||||
</label>
|
||||
<input type="text" class="form-control config-static" id="static-BK_PURCHASE_DATE_FORMAT" data-var="BK_PURCHASE_DATE_FORMAT">
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label for="static-BK_PURCHASE_CURRENCY" class="form-label">
|
||||
BK_PURCHASE_CURRENCY {{ config_badges('BK_PURCHASE_CURRENCY') }}
|
||||
<div class="text-muted small">Purchase currency</div>
|
||||
</label>
|
||||
<input type="text" class="form-control config-static" id="static-BK_PURCHASE_CURRENCY" data-var="BK_PURCHASE_CURRENCY">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Socket Configuration -->
|
||||
<h6 class="fw-bold text-secondary border-bottom pb-1 mb-3 mt-4">Socket Configuration</h6>
|
||||
<div class="row g-3">
|
||||
<div class="col-md-6">
|
||||
<label for="static-BK_SOCKET_NAMESPACE" class="form-label">
|
||||
BK_SOCKET_NAMESPACE {{ config_badges('BK_SOCKET_NAMESPACE') }}
|
||||
<div class="text-muted small">Socket.IO namespace</div>
|
||||
</label>
|
||||
<input type="text" class="form-control config-static" id="static-BK_SOCKET_NAMESPACE" data-var="BK_SOCKET_NAMESPACE">
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label for="static-BK_SOCKET_PATH" class="form-label">
|
||||
BK_SOCKET_PATH {{ config_badges('BK_SOCKET_PATH') }}
|
||||
<div class="text-muted small">Socket.IO path</div>
|
||||
</label>
|
||||
<input type="text" class="form-control config-static" id="static-BK_SOCKET_PATH" data-var="BK_SOCKET_PATH">
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="form-check form-switch">
|
||||
<input class="form-check-input config-static-toggle" type="checkbox" id="static-BK_NO_THREADED_SOCKET" data-var="BK_NO_THREADED_SOCKET">
|
||||
<label class="form-check-label" for="static-BK_NO_THREADED_SOCKET">
|
||||
BK_NO_THREADED_SOCKET {{ config_badges('BK_NO_THREADED_SOCKET') }}
|
||||
<div class="text-muted small">Disable threaded socket mode</div>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Save Button for Static Settings -->
|
||||
<div class="d-flex gap-2 justify-content-end mt-4">
|
||||
<button id="config-save-static" class="btn btn-warning">
|
||||
<i class="ri-save-line"></i> Save Static Settings to .env
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{ accordion.footer() }}
|
||||
|
||||
<!-- Initialize Configuration Data -->
|
||||
<script type="text/javascript">
|
||||
window.CURRENT_CONFIG = {
|
||||
{% for env_name, value in env_values.items() %}
|
||||
'{{ env_name }}': {{ value|tojson }},
|
||||
{% endfor %}
|
||||
};
|
||||
|
||||
window.DEFAULT_CONFIG = {
|
||||
{% for env_name, value in config_defaults.items() %}
|
||||
'{{ env_name }}': {{ value|tojson }},
|
||||
{% endfor %}
|
||||
};
|
||||
|
||||
window.EXPLICIT_VALUES = {
|
||||
{% for env_name, is_explicit in env_explicit_values.items() %}
|
||||
'{{ env_name }}': {{ is_explicit|tojson }},
|
||||
{% endfor %}
|
||||
};
|
||||
</script>
|
||||
|
||||
<!-- Configuration Management JavaScript -->
|
||||
<script src="{{ url_for('static', filename='scripts/admin_config.js') }}"></script>
|
||||
|
||||
Reference in New Issue
Block a user