Branding customization

This commit is contained in:
2026-03-30 14:35:30 -04:00
parent 53eaf1c76d
commit a4e605297a
7 changed files with 295 additions and 43 deletions
+89 -23
View File
@@ -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)
+12
View File
@@ -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)