mirror of
https://github.com/DRYTRIX/TimeTracker.git
synced 2026-05-21 05:40:26 -05:00
9112a696dd
- Improve audit logging error messages to distinguish table missing errors from other failures - Add warning-level logging for audit_logs table missing scenarios with migration guidance - Update audit event listener with better error detection and logging - Add comprehensive diagnostic script for checking audit logging setup - Update UI templates (base.html, admin forms, user settings, profile pages) - Extend audit logging support across routes (admin, api, permissions, reports, timer, user) - Add extensive test coverage for admin user management functionality - Update time tracking service and user model with audit logging integration
143 lines
9.4 KiB
HTML
143 lines
9.4 KiB
HTML
{% extends "base.html" %}
|
|
|
|
{% block content %}
|
|
<div class="flex flex-col md:flex-row justify-between items-start md:items-center mb-6">
|
|
<div>
|
|
<h1 class="text-2xl font-bold">{{ 'Edit User' if user else 'Create User' }}</h1>
|
|
<p class="text-text-muted-light dark:text-text-muted-dark">
|
|
{{ 'Update the details for %s.'|format(user.username) if user else 'Create a new user account.' }}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="bg-card-light dark:bg-card-dark p-6 rounded-lg shadow">
|
|
<form method="POST">
|
|
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
|
|
<div class="space-y-6">
|
|
<div>
|
|
<label for="username" class="block text-sm font-medium text-gray-700 dark:text-gray-300">Username</label>
|
|
<input type="text" name="username" id="username" value="{{ user.username if user else '' }}" required class="form-input">
|
|
</div>
|
|
<div>
|
|
<label for="role" class="block text-sm font-medium text-gray-700 dark:text-gray-300">Role</label>
|
|
<select name="role" id="role" class="form-input">
|
|
{% if all_roles %}
|
|
{% for role in all_roles %}
|
|
<option value="{{ role.name }}" {% if user and user.roles and user.roles|length > 0 and role == user.roles[0] %}selected{% elif user and not user.roles and role.name == user.role %}selected{% elif not user and role.name == 'user' %}selected{% endif %}>
|
|
{{ role.name|capitalize }}{% if role.is_system_role %} (System){% endif %}
|
|
</option>
|
|
{% endfor %}
|
|
{% else %}
|
|
{# Fallback to legacy roles if Role system not initialized #}
|
|
<option value="user" {% if user and user.role == 'user' %}selected{% endif %}>User</option>
|
|
<option value="admin" {% if user and user.role == 'admin' %}selected{% endif %}>Admin</option>
|
|
{% endif %}
|
|
</select>
|
|
<p class="text-xs text-text-muted-light dark:text-text-muted-dark mt-1">
|
|
Select the primary role for this user. Additional roles can be assigned via "Manage Roles & Permissions" below.
|
|
</p>
|
|
</div>
|
|
{% if not user %}
|
|
<div>
|
|
<label for="default_password" class="block text-sm font-medium text-gray-700 dark:text-gray-300">Default Password</label>
|
|
<input type="password" name="default_password" id="default_password" class="form-input" placeholder="Leave empty for no password">
|
|
<p class="text-xs text-text-muted-light dark:text-text-muted-dark mt-1">
|
|
Set an initial password for this user. If set, you can require them to change it on first login.
|
|
</p>
|
|
</div>
|
|
<div class="flex items-center">
|
|
<input type="checkbox" name="force_password_change" id="force_password_change" class="h-4 w-4 rounded border-gray-300 text-indigo-600 shadow-sm focus:border-indigo-500 focus:ring-indigo-500">
|
|
<label for="force_password_change" class="ml-2 block text-sm text-gray-900 dark:text-gray-300">Require password change on first login</label>
|
|
</div>
|
|
{% endif %}
|
|
{% if user %}
|
|
<div class="flex items-center">
|
|
<input type="checkbox" name="is_active" id="is_active" {% if user.is_active %}checked{% endif %} class="h-4 w-4 rounded border-gray-300 text-indigo-600 shadow-sm focus:border-indigo-500 focus:ring-indigo-500">
|
|
<label for="is_active" class="ml-2 block text-sm text-gray-900 dark:text-gray-300">Active</label>
|
|
</div>
|
|
|
|
<div class="border border-border-light dark:border-border-dark rounded-lg p-4 bg-bg-secondary-light dark:bg-bg-secondary-dark">
|
|
<h3 class="font-semibold mb-2">{{ _('Password Reset') }}</h3>
|
|
<p class="text-sm text-text-muted-light dark:text-text-muted-dark mb-3">
|
|
Reset the password for this user. Leave blank to keep the current password.
|
|
</p>
|
|
<div class="space-y-3">
|
|
<div>
|
|
<label for="new_password" class="block text-sm font-medium text-gray-700 dark:text-gray-300">New Password</label>
|
|
<input type="password" name="new_password" id="new_password" class="form-input" placeholder="{{ _('Leave blank to keep current password') }}">
|
|
<p class="text-xs text-text-muted-light dark:text-text-muted-dark mt-1">
|
|
Password must be at least 8 characters long.
|
|
</p>
|
|
</div>
|
|
<div>
|
|
<label for="password_confirm" class="block text-sm font-medium text-gray-700 dark:text-gray-300">Confirm New Password</label>
|
|
<input type="password" name="password_confirm" id="password_confirm" class="form-input">
|
|
</div>
|
|
<div class="flex items-center">
|
|
<input type="checkbox" name="force_password_change" id="force_password_change" {% if user.password_change_required %}checked{% endif %} class="h-4 w-4 rounded border-gray-300 text-indigo-600 shadow-sm focus:border-indigo-500 focus:ring-indigo-500">
|
|
<label for="force_password_change" class="ml-2 block text-sm text-gray-900 dark:text-gray-300">Require password change on next login</label>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="border border-border-light dark:border-border-dark rounded-lg p-4 bg-bg-secondary-light dark:bg-bg-secondary-dark">
|
|
<h3 class="font-semibold mb-2">{{ _('Client Portal Access') }}</h3>
|
|
<p class="text-sm text-text-muted-light dark:text-text-muted-dark mb-3">
|
|
Enable client portal access for this user. When enabled, the user will only see data for their assigned client.
|
|
</p>
|
|
<div class="space-y-3">
|
|
<div class="flex items-center">
|
|
<input type="checkbox" name="client_portal_enabled" id="client_portal_enabled" {% if user.client_portal_enabled %}checked{% endif %} class="h-4 w-4 rounded border-gray-300 text-indigo-600 shadow-sm focus:border-indigo-500 focus:ring-indigo-500" onchange="toggleClientSelect()">
|
|
<label for="client_portal_enabled" class="ml-2 block text-sm text-gray-900 dark:text-gray-300">Enable Client Portal</label>
|
|
</div>
|
|
<div id="client_select_container" style="display: {% if user.client_portal_enabled %}block{% else %}none{% endif %};">
|
|
<label for="client_id" class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">Assign Client</label>
|
|
<select name="client_id" id="client_id" class="form-input">
|
|
<option value="">-- Select Client --</option>
|
|
{% for client in clients %}
|
|
<option value="{{ client.id }}" {% if user.client_id == client.id %}selected{% endif %}>{{ client.name }}</option>
|
|
{% endfor %}
|
|
</select>
|
|
<p class="text-xs text-text-muted-light dark:text-text-muted-dark mt-1">Select the client this user should have access to.</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="border border-border-light dark:border-border-dark rounded-lg p-4 bg-bg-secondary-light dark:bg-bg-secondary-dark">
|
|
<h3 class="font-semibold mb-2">{{ _('Roles & Permissions') }}</h3>
|
|
<p class="text-sm text-text-muted-light dark:text-text-muted-dark mb-3">
|
|
Manage fine-grained role-based permissions for this user. You can assign multiple roles and customize permissions.
|
|
</p>
|
|
<a href="{{ url_for('permissions.manage_user_roles', user_id=user.id) }}" class="inline-block bg-primary text-white px-4 py-2 rounded-lg text-sm hover:bg-opacity-90">
|
|
Manage Roles & Permissions
|
|
</a>
|
|
<div class="mt-3 text-xs text-text-muted-light dark:text-text-muted-dark">
|
|
{% if user.roles %}
|
|
<strong>Current roles:</strong> {{ user.get_role_names()|join(', ') }}
|
|
{% else %}
|
|
<strong>Current role:</strong> {{ user.role|capitalize }} (legacy)
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
<div class="mt-8 border-t border-border-light dark:border-border-dark pt-6 flex justify-end">
|
|
<a href="{{ url_for('admin.list_users') }}" class="bg-gray-200 dark:bg-gray-700 px-4 py-2 rounded-lg mr-4">Cancel</a>
|
|
<button type="submit" class="bg-primary text-white px-4 py-2 rounded-lg">Save</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
|
|
<script>
|
|
function toggleClientSelect() {
|
|
const checkbox = document.getElementById('client_portal_enabled');
|
|
const container = document.getElementById('client_select_container');
|
|
if (checkbox.checked) {
|
|
container.style.display = 'block';
|
|
} else {
|
|
container.style.display = 'none';
|
|
}
|
|
}
|
|
</script>
|
|
{% endblock %}
|