Files
enterprise_digital-platform/IT_asset_management/app/templates/settings/index.html
T
ske087 7d24e7f527 IT Assets: add role-based auth system and portal user sync
Auth:
- Fix local login (was redirecting to portal; now authenticates AdminUser directly)
- Portal SSO still takes priority in production via nginx headers

Role system (admin | editor | readonly):
- New app/utils/decorators.py with editor_required and admin_required decorators
- All write routes protected with editor_required (create/edit/delete/import/mask)
- Settings user management protected with admin_required
- Sidebar hides write-only links for readonly users
- Dashboard quick actions and list page buttons hidden for readonly

Settings page:
- Role colour badges (admin=red, editor=blue, readonly=grey)
- Inline role changer per user (dropdown auto-submit)
- Reset password modal per user
- Delete user button with confirmation
- Add user form includes role selector with legend

Portal user sync:
- New /internal/sync-user endpoint receives user pre-creation from portal
- INTERNAL_SYNC_SECRET added to config
- portal/config.py: added internal_url for itassets app so _sync_user_to_app works
2026-07-08 21:35:16 +03:00

198 lines
9.1 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{% extends 'base.html' %}
{% block title %}Settings IT Asset Management{% endblock %}
{% block breadcrumb %}
<li class="breadcrumb-item"><a href="{{ url_for('dashboard.index') }}">Home</a></li>
<li class="breadcrumb-item active">Settings</li>
{% endblock %}
{% block content %}
<div class="page-header mb-4">
<h1><i class="bi bi-gear me-2"></i>Settings</h1>
</div>
<div class="row g-4">
<!-- App Users -->
<div class="col-md-7">
<div class="card border-0 shadow-sm">
<div class="card-header bg-white fw-semibold py-3">
<i class="bi bi-person-gear me-2 text-primary"></i>Application Users
</div>
<div class="table-responsive">
<table class="table table-sm table-hover mb-0">
<thead class="table-light">
<tr><th>Username</th><th>Full Name</th><th>Email</th><th>Role</th><th>Last Login</th><th>Active</th>
{% if current_user.is_admin %}<th></th>{% endif %}
</tr>
</thead>
<tbody>
{% for a in admins %}
<tr>
<td><strong>{{ a.username }}</strong>{% if a.id == current_user.id %} <span class="badge bg-light text-secondary">you</span>{% endif %}</td>
<td>{{ a.full_name or '—' }}</td>
<td>{{ a.email }}</td>
<td>
<span class="badge {% if a.role == 'admin' %}bg-danger{% elif a.role == 'editor' %}bg-primary{% else %}bg-secondary{% endif %}">
{{ a.role }}
</span>
</td>
<td>{{ a.last_login.strftime('%d/%m/%Y') if a.last_login else '—' }}</td>
<td>
{% if a.is_active %}<span class="badge bg-success">Active</span>
{% else %}<span class="badge bg-secondary">Inactive</span>{% endif %}
</td>
{% if current_user.is_admin %}
<td class="text-end">
{% if a.id != current_user.id %}
<div class="d-flex gap-1 justify-content-end flex-wrap">
<!-- Toggle active -->
<form method="POST" action="{{ url_for('settings.toggle_admin', admin_id=a.id) }}" class="d-inline">
<button type="submit" class="btn btn-xs btn-sm btn-outline-{{ 'warning' if a.is_active else 'success' }} py-0 px-2">
{{ 'Deactivate' if a.is_active else 'Activate' }}
</button>
</form>
<!-- Change role -->
<form method="POST" action="{{ url_for('settings.change_role', admin_id=a.id) }}" class="d-inline">
<select name="role" class="form-select form-select-sm d-inline-block w-auto py-0" onchange="this.form.submit()">
<option value="admin" {% if a.role == 'admin' %}selected{% endif %}>admin</option>
<option value="editor" {% if a.role == 'editor' %}selected{% endif %}>editor</option>
<option value="readonly" {% if a.role == 'readonly' %}selected{% endif %}>readonly</option>
</select>
</form>
<!-- Reset password -->
<button type="button" class="btn btn-xs btn-sm btn-outline-secondary py-0 px-2"
data-bs-toggle="modal" data-bs-target="#pwModal{{ a.id }}">
<i class="bi bi-key"></i>
</button>
<!-- Delete -->
<form method="POST" action="{{ url_for('settings.delete_admin', admin_id=a.id) }}" class="d-inline"
onsubmit="return confirm('Delete user {{ a.username }}? This cannot be undone.')">
<button type="submit" class="btn btn-xs btn-sm btn-outline-danger py-0 px-2">
<i class="bi bi-trash"></i>
</button>
</form>
</div>
{% endif %}
</td>
{% endif %}
</tr>
{% endfor %}
</tbody>
</table>
</div>
<!-- Add user form (admin only) -->
{% if current_user.is_admin %}
<div class="card-footer bg-white">
<h6 class="fw-semibold mb-3 mt-1"><i class="bi bi-person-plus me-1"></i>Add User</h6>
<form method="POST" action="{{ url_for('settings.create_admin') }}">
<div class="row g-2">
<div class="col-md-3">
<input type="text" name="username" class="form-control form-control-sm" placeholder="Username" required>
</div>
<div class="col-md-3">
<input type="text" name="full_name" class="form-control form-control-sm" placeholder="Full Name">
</div>
<div class="col-md-3">
<input type="email" name="email" class="form-control form-control-sm" placeholder="Email" required>
</div>
<div class="col-md-2">
<input type="password" name="password" class="form-control form-control-sm" placeholder="Password" required minlength="8">
</div>
<div class="col-md-2">
<select name="role" class="form-select form-select-sm">
<option value="readonly">readonly</option>
<option value="editor">editor</option>
<option value="admin">admin</option>
</select>
</div>
<div class="col-auto">
<button type="submit" class="btn btn-sm btn-primary">Add</button>
</div>
</div>
<small class="text-muted mt-1 d-block">
<strong>readonly</strong> view only &nbsp;|&nbsp;
<strong>editor</strong> create &amp; edit data &nbsp;|&nbsp;
<strong>admin</strong> full access including user management
</small>
</form>
</div>
{% endif %}
</div>
</div>
<!-- LDAP config info -->
<div class="col-md-5">
<div class="card border-0 shadow-sm">
<div class="card-header bg-white fw-semibold py-3">
<i class="bi bi-diagram-3 me-2 text-primary"></i>LDAP / AD Configuration
</div>
<div class="card-body">
<p class="text-muted small mb-3">
LDAP settings are managed via environment variables (see <code>.env</code> file).
Restart the application after changing these values.
</p>
<table class="table table-sm table-bordered mb-0">
<tbody>
<tr><th class="bg-light">LDAP_SERVER</th><td><code>{{ config.LDAP_SERVER or '(not set)' }}</code></td></tr>
<tr><th class="bg-light">LDAP_PORT</th><td>{{ config.LDAP_PORT }}</td></tr>
<tr><th class="bg-light">LDAP_USE_SSL</th><td>{{ config.LDAP_USE_SSL }}</td></tr>
<tr><th class="bg-light">LDAP_BASE_DN</th><td><code>{{ config.LDAP_BASE_DN or '(not set)' }}</code></td></tr>
<tr><th class="bg-light">LDAP_BIND_USER</th><td>{{ config.LDAP_BIND_USER or '(not set)' }}</td></tr>
<tr><th class="bg-light">Windows ID attr</th><td><code>{{ config.LDAP_WINDOWS_ID_ATTR }}</code></td></tr>
</tbody>
</table>
<div class="mt-3">
{% if current_user.is_editor %}
<a href="{{ url_for('users.import_page') }}" class="btn btn-sm btn-outline-primary">
<i class="bi bi-arrow-repeat me-1"></i>Go to Import / Sync
</a>
{% endif %}
</div>
</div>
</div>
<div class="card border-0 shadow-sm mt-3">
<div class="card-header bg-white fw-semibold py-3">
<i class="bi bi-building me-2 text-primary"></i>Company Info (for PDFs)
</div>
<div class="card-body">
<table class="table table-sm table-bordered mb-0">
<tbody>
<tr><th class="bg-light">COMPANY_NAME</th><td>{{ config.COMPANY_NAME or '(not set)' }}</td></tr>
<tr><th class="bg-light">COMPANY_ADDRESS</th><td>{{ config.COMPANY_ADDRESS or '(not set)' }}</td></tr>
</tbody>
</table>
<p class="small text-muted mt-2 mb-0">Edit these in <code>.env</code> and restart.</p>
</div>
</div>
</div>
</div>
<!-- Password reset modals (one per user, admin only) -->
{% if current_user.is_admin %}
{% for a in admins %}
{% if a.id != current_user.id %}
<div class="modal fade" id="pwModal{{ a.id }}" tabindex="-1">
<div class="modal-dialog modal-sm">
<div class="modal-content">
<form method="POST" action="{{ url_for('settings.reset_password', admin_id=a.id) }}">
<div class="modal-header">
<h6 class="modal-title">Reset password — {{ a.username }}</h6>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<input type="password" name="new_password" class="form-control" placeholder="New password (min 8 chars)" required minlength="8">
</div>
<div class="modal-footer">
<button type="button" class="btn btn-sm btn-secondary" data-bs-dismiss="modal">Cancel</button>
<button type="submit" class="btn btn-sm btn-primary">Reset</button>
</div>
</form>
</div>
</div>
</div>
{% endif %}
{% endfor %}
{% endif %}
{% endblock %}