204 lines
9.2 KiB
HTML
204 lines
9.2 KiB
HTML
{% extends "base.html" %}
|
|
{% block title %}System Settings — {{ branding.app_name }}{% endblock %}
|
|
|
|
{% block content %}
|
|
<div class="page-header" style="margin-bottom:28px;">
|
|
<div>
|
|
<h1 class="page-title">System Settings</h1>
|
|
<p class="page-subtitle">Manage application-wide configuration and branding.</p>
|
|
</div>
|
|
</div>
|
|
|
|
{% with messages = get_flashed_messages(with_categories=true) %}
|
|
{% for cat, msg in messages %}
|
|
<div class="alert alert-{{ cat }} alert-dismissible fade show" role="alert">
|
|
{{ msg }}
|
|
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
|
|
</div>
|
|
{% endfor %}
|
|
{% endwith %}
|
|
|
|
<!-- Branding -->
|
|
<div class="card mb-4" style="max-width:680px;">
|
|
<div class="card-header" style="padding:16px 20px;border-bottom:1px solid var(--border);">
|
|
<h5 class="mb-0" style="font-size:15px;font-weight:600;">
|
|
<i class="bi bi-palette me-2"></i>Branding & Appearance
|
|
</h5>
|
|
</div>
|
|
<div class="card-body" style="padding:20px;">
|
|
<form method="POST" action="{{ url_for('admin.settings') }}" enctype="multipart/form-data">
|
|
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
|
|
<input type="hidden" name="form_type" value="branding">
|
|
<input type="hidden" name="remove_logo" id="remove-logo-input" value="0">
|
|
|
|
<!-- Logo preview + upload -->
|
|
<div class="mb-4 d-flex align-items-center gap-4">
|
|
<div id="logo-preview-wrap"
|
|
style="width:64px;height:64px;border-radius:12px;background:{{ branding_settings.primary_color }};display:flex;align-items:center;justify-content:center;flex-shrink:0;overflow:hidden;box-shadow:0 2px 8px rgba(0,0,0,.12);">
|
|
{% if branding_settings.logo_stored_name %}
|
|
<img id="logo-preview-img"
|
|
src="{{ url_for('auth.serve_logo', filename=branding_settings.logo_stored_name) }}"
|
|
alt="Logo" style="width:64px;height:64px;object-fit:contain;padding:4px;"/>
|
|
{% else %}
|
|
<span id="logo-preview-initials"
|
|
style="font-family:'Space Mono',monospace;font-weight:700;font-size:18px;color:#fff;">
|
|
{{ branding_settings.logo_initials[:2] }}
|
|
</span>
|
|
{% endif %}
|
|
</div>
|
|
<div style="flex:1;">
|
|
<label class="form-label" style="font-size:12px;font-weight:600;">
|
|
Company Logo
|
|
<span style="color:var(--muted);font-weight:400;">(PNG, JPG, GIF, WebP — max 2 MB)</span>
|
|
</label>
|
|
<input type="file" class="form-control form-control-sm" name="logo_file"
|
|
id="logo-file-input" accept=".png,.jpg,.jpeg,.gif,.webp"/>
|
|
{% if branding_settings.logo_stored_name %}
|
|
<div class="mt-2" id="remove-logo-row">
|
|
<button type="button" class="btn btn-sm btn-outline-danger" onclick="removeLogo()">
|
|
<i class="bi bi-trash me-1"></i>Remove current logo
|
|
</button>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
|
|
<div class="row g-3 mb-3">
|
|
<div class="col-md-6">
|
|
<label class="form-label">Application Name</label>
|
|
<input type="text" class="form-control" name="app_name"
|
|
value="{{ branding_settings.app_name }}" maxlength="60" required/>
|
|
<div class="form-text">Shown in the sidebar and browser tab.</div>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<label class="form-label">App Subtitle</label>
|
|
<input type="text" class="form-control" name="app_subtitle"
|
|
value="{{ branding_settings.app_subtitle }}" maxlength="80"/>
|
|
<div class="form-text">Shown below the app name in the sidebar.</div>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<label class="form-label">Company Name</label>
|
|
<input type="text" class="form-control" name="company_name"
|
|
value="{{ branding_settings.company_name }}" maxlength="100"/>
|
|
<div class="form-text">Shown on the login and register pages.</div>
|
|
</div>
|
|
<div class="col-md-3">
|
|
<label class="form-label">Logo Initials</label>
|
|
<input type="text" class="form-control" name="logo_initials" id="logo-initials"
|
|
value="{{ branding_settings.logo_initials }}" maxlength="3"
|
|
oninput="updateInitialsPreview(this.value)"/>
|
|
<div class="form-text">Fallback when no logo is uploaded.</div>
|
|
</div>
|
|
<div class="col-md-3">
|
|
<label class="form-label">Primary Colour</label>
|
|
<div class="d-flex gap-2 align-items-center">
|
|
<input type="color" class="form-control form-control-color" name="primary_color"
|
|
id="primary-color-input"
|
|
value="{{ branding_settings.primary_color }}"
|
|
oninput="updateColorPreview(this.value)"
|
|
style="width:48px;height:38px;padding:2px;cursor:pointer;"/>
|
|
<input type="text" class="form-control form-control-sm" id="primary-color-text"
|
|
value="{{ branding_settings.primary_color }}" maxlength="7"
|
|
style="font-family:'Space Mono',monospace;font-size:12px;"
|
|
oninput="syncColorPicker(this.value)"/>
|
|
</div>
|
|
<div class="form-text">Accent colour across the interface.</div>
|
|
</div>
|
|
</div>
|
|
|
|
<button type="submit" class="btn btn-primary">
|
|
<i class="bi bi-check2 me-2"></i>Save Branding
|
|
</button>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Registration -->
|
|
<div class="card" style="max-width:680px;">
|
|
<div class="card-header" style="padding:16px 20px;border-bottom:1px solid var(--border);">
|
|
<h5 class="mb-0" style="font-size:15px;font-weight:600;">
|
|
<i class="bi bi-person-gear me-2"></i>User Registration
|
|
</h5>
|
|
</div>
|
|
<div class="card-body" style="padding:20px;">
|
|
<p style="font-size:13px;color:var(--muted);margin-bottom:20px;">
|
|
When disabled, the <code>/auth/register</code> endpoint redirects all visitors to the login
|
|
page. Existing accounts and admin-created accounts are unaffected.
|
|
</p>
|
|
<form method="POST" action="{{ url_for('admin.settings') }}">
|
|
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
|
|
<input type="hidden" name="form_type" value="registration">
|
|
<div class="d-flex align-items-center justify-content-between p-3"
|
|
style="border:1px solid var(--border);border-radius:10px;background:var(--surface);">
|
|
<div>
|
|
<div style="font-weight:600;font-size:14px;">
|
|
<i class="bi bi-person-plus me-2"></i>Allow Self-Registration
|
|
</div>
|
|
<div style="font-size:12px;color:var(--muted);margin-top:3px;">
|
|
Permits new employees to create their own accounts via the Register page.
|
|
</div>
|
|
</div>
|
|
<div class="d-flex align-items-center gap-3" style="flex-shrink:0;margin-left:16px;">
|
|
<span class="badge {% if registration_enabled %}bg-success{% else %}bg-secondary{% endif %}"
|
|
style="font-size:11px;padding:5px 10px;">
|
|
{{ 'Enabled' if registration_enabled else 'Disabled' }}
|
|
</span>
|
|
{% if registration_enabled %}
|
|
<button type="submit" name="registration_enabled" value="0"
|
|
class="btn btn-sm btn-outline-danger"
|
|
onclick="return confirm('Disable self-registration? New users will not be able to sign up independently.')">
|
|
<i class="bi bi-toggle-on me-1"></i>Disable
|
|
</button>
|
|
{% else %}
|
|
<button type="submit" name="registration_enabled" value="1"
|
|
class="btn btn-sm btn-outline-success">
|
|
<i class="bi bi-toggle-off me-1"></i>Enable
|
|
</button>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
document.getElementById('logo-file-input').addEventListener('change', function() {
|
|
const file = this.files[0];
|
|
if (!file) return;
|
|
const reader = new FileReader();
|
|
reader.onload = e => {
|
|
document.getElementById('logo-preview-wrap').innerHTML =
|
|
`<img src="${e.target.result}" style="width:64px;height:64px;object-fit:contain;padding:4px;"/>`;
|
|
};
|
|
reader.readAsDataURL(file);
|
|
});
|
|
|
|
function updateInitialsPreview(val) {
|
|
const el = document.getElementById('logo-preview-initials');
|
|
if (el) el.textContent = val.substring(0, 2).toUpperCase();
|
|
}
|
|
|
|
function updateColorPreview(hex) {
|
|
document.getElementById('primary-color-text').value = hex;
|
|
document.getElementById('logo-preview-wrap').style.background = hex;
|
|
}
|
|
|
|
function syncColorPicker(val) {
|
|
if (/^#[0-9a-fA-F]{6}$/.test(val)) {
|
|
document.getElementById('primary-color-input').value = val;
|
|
document.getElementById('logo-preview-wrap').style.background = val;
|
|
}
|
|
}
|
|
|
|
function removeLogo() {
|
|
if (!confirm('Remove the current logo?')) return;
|
|
document.getElementById('remove-logo-input').value = '1';
|
|
const initials = document.getElementById('logo-initials').value.substring(0, 2).toUpperCase();
|
|
document.getElementById('logo-preview-wrap').innerHTML =
|
|
`<span id="logo-preview-initials" style="font-family:'Space Mono',monospace;font-weight:700;font-size:18px;color:#fff;">${initials}</span>`;
|
|
const row = document.getElementById('remove-logo-row');
|
|
if (row) row.style.display = 'none';
|
|
}
|
|
</script>
|
|
{% endblock %}
|