Branding customization
This commit is contained in:
+89
-23
@@ -851,29 +851,95 @@ def settings():
|
||||
from app.models import SystemSetting
|
||||
|
||||
if request.method == 'POST':
|
||||
new_value = '1' if request.form.get('registration_enabled') == '1' else '0'
|
||||
old_value = SystemSetting.get('registration_enabled', 'true')
|
||||
SystemSetting.set(
|
||||
'registration_enabled',
|
||||
new_value,
|
||||
'Allow new users to self-register via /auth/register'
|
||||
)
|
||||
db.session.commit()
|
||||
form_type = request.form.get('form_type')
|
||||
|
||||
state_label = 'enabled' if new_value == '1' else 'disabled'
|
||||
log_action(
|
||||
current_user.id,
|
||||
'setting_update',
|
||||
'system_setting',
|
||||
None,
|
||||
f'registration_enabled changed from {old_value} to {new_value} by admin_id={current_user.id}'
|
||||
)
|
||||
logger.info(
|
||||
f'[ADMIN SETTINGS] registration_enabled={new_value} '
|
||||
f'by admin_id={current_user.id} email={current_user.email}'
|
||||
)
|
||||
flash(f'User registration has been {state_label}.', 'success')
|
||||
return redirect(url_for('admin.settings'))
|
||||
# ── Registration toggle ───────────────────────────────────────────────
|
||||
if form_type == 'registration':
|
||||
new_value = '1' if request.form.get('registration_enabled') == '1' else '0'
|
||||
old_value = SystemSetting.get('registration_enabled', 'true')
|
||||
SystemSetting.set('registration_enabled', new_value,
|
||||
'Allow new users to self-register via /auth/register')
|
||||
db.session.commit()
|
||||
state_label = 'enabled' if new_value == '1' else 'disabled'
|
||||
log_action(current_user.id, 'setting_update', 'system_setting', None,
|
||||
f'registration_enabled changed from {old_value} to {new_value} by admin_id={current_user.id}')
|
||||
logger.info(f'[ADMIN SETTINGS] registration_enabled={new_value} by admin_id={current_user.id}')
|
||||
flash(f'User registration has been {state_label}.', 'success')
|
||||
return redirect(url_for('admin.settings'))
|
||||
|
||||
# ── Branding update ───────────────────────────────────────────────────
|
||||
if form_type == 'branding':
|
||||
fields = {
|
||||
'app_name' : ('Application name', request.form.get('app_name', '').strip()),
|
||||
'app_subtitle' : ('App subtitle', request.form.get('app_subtitle', '').strip()),
|
||||
'company_name' : ('Company name', request.form.get('company_name', '').strip()),
|
||||
'logo_initials': ('Logo initials', request.form.get('logo_initials', '').strip()[:3]),
|
||||
'primary_color': ('Primary colour', request.form.get('primary_color', '#2563eb').strip()),
|
||||
}
|
||||
changes = []
|
||||
for key, (label, new_val) in fields.items():
|
||||
old_val = SystemSetting.get(key, '')
|
||||
if new_val != old_val:
|
||||
SystemSetting.set(key, new_val)
|
||||
changes.append(f'{key}={new_val!r}')
|
||||
|
||||
# ── Logo file upload ──────────────────────────────────────────────
|
||||
logo_file = request.files.get('logo_file')
|
||||
if logo_file and logo_file.filename:
|
||||
from app.services.validation_service import validate_file
|
||||
from werkzeug.utils import secure_filename
|
||||
allowed = {'png', 'jpg', 'jpeg', 'gif', 'webp'}
|
||||
file_error = validate_file(logo_file, allowed)
|
||||
if file_error:
|
||||
flash(f'Logo not saved: {file_error}', 'danger')
|
||||
else:
|
||||
logo_file.stream.seek(0, 2)
|
||||
logo_size = logo_file.stream.tell()
|
||||
logo_file.stream.seek(0)
|
||||
if logo_size > 2 * 1024 * 1024:
|
||||
flash('Logo image must be under 2 MB.', 'danger')
|
||||
else:
|
||||
ext = secure_filename(logo_file.filename).rsplit('.', 1)[-1].lower()
|
||||
stored_name = f"logo_{uuid.uuid4().hex}.{ext}"
|
||||
upload_dir = current_app.config['UPLOAD_FOLDER']
|
||||
# Delete old logo from disk
|
||||
old_logo = SystemSetting.get('logo_stored_name', '')
|
||||
if old_logo:
|
||||
old_path = os.path.join(upload_dir, old_logo)
|
||||
if os.path.exists(old_path):
|
||||
os.remove(old_path)
|
||||
logo_file.save(os.path.join(upload_dir, stored_name))
|
||||
SystemSetting.set('logo_stored_name', stored_name, 'Stored filename of the uploaded company logo image')
|
||||
changes.append(f'logo_stored_name={stored_name!r}')
|
||||
logger.info(f'[ADMIN BRANDING] logo uploaded file={stored_name} by admin_id={current_user.id}')
|
||||
|
||||
# ── Logo removal ──────────────────────────────────────────────────
|
||||
if request.form.get('remove_logo') == '1':
|
||||
old_logo = SystemSetting.get('logo_stored_name', '')
|
||||
if old_logo:
|
||||
old_path = os.path.join(current_app.config['UPLOAD_FOLDER'], old_logo)
|
||||
if os.path.exists(old_path):
|
||||
os.remove(old_path)
|
||||
SystemSetting.set('logo_stored_name', '')
|
||||
changes.append('logo_stored_name removed')
|
||||
|
||||
db.session.commit()
|
||||
if changes:
|
||||
log_action(current_user.id, 'branding_update', 'system_setting', None,
|
||||
f'changes: {", ".join(changes)} by admin_id={current_user.id}')
|
||||
logger.info(f'[ADMIN BRANDING] {", ".join(changes)} by admin_id={current_user.id}')
|
||||
flash('Branding settings saved.', 'success')
|
||||
return redirect(url_for('admin.settings'))
|
||||
|
||||
registration_enabled = SystemSetting.get_bool('registration_enabled', default=True)
|
||||
return render_template('admin/settings.html', registration_enabled=registration_enabled)
|
||||
branding_settings = {
|
||||
'app_name' : SystemSetting.get('app_name', 'TechDesk'),
|
||||
'app_subtitle' : SystemSetting.get('app_subtitle', 'IT Helpdesk System'),
|
||||
'company_name' : SystemSetting.get('company_name', ''),
|
||||
'logo_stored_name': SystemSetting.get('logo_stored_name', ''),
|
||||
'logo_initials' : SystemSetting.get('logo_initials', 'TD'),
|
||||
'primary_color' : SystemSetting.get('primary_color', '#2563eb'),
|
||||
}
|
||||
return render_template('admin/settings.html',
|
||||
registration_enabled=registration_enabled,
|
||||
branding_settings=branding_settings)
|
||||
@@ -17,6 +17,9 @@ logger = logging.getLogger(__name__)
|
||||
AVATAR_ALLOWED_EXT = {'png', 'jpg', 'jpeg', 'gif', 'webp'}
|
||||
AVATAR_MAX_BYTES = 5 * 1024 * 1024 # 5 MB
|
||||
|
||||
LOGO_ALLOWED_EXT = {'png', 'jpg', 'jpeg', 'gif', 'webp', 'svg'}
|
||||
LOGO_MAX_BYTES = 2 * 1024 * 1024 # 2 MB
|
||||
|
||||
|
||||
def _is_safe_url(target):
|
||||
"""Return True only when *target* points back to this same host.
|
||||
@@ -195,3 +198,12 @@ def serve_avatar(filename):
|
||||
abort(400)
|
||||
upload_dir = current_app.config['UPLOAD_FOLDER']
|
||||
return send_from_directory(upload_dir, filename, as_attachment=False)
|
||||
|
||||
|
||||
@auth_bp.route('/logo/<string:filename>')
|
||||
def serve_logo(filename):
|
||||
"""Serve the company logo — publicly accessible (shown on login page)."""
|
||||
if '/' in filename or '\\' in filename or '..' in filename:
|
||||
abort(400)
|
||||
upload_dir = current_app.config['UPLOAD_FOLDER']
|
||||
return send_from_directory(upload_dir, filename, as_attachment=False)
|
||||
|
||||
Reference in New Issue
Block a user