feat: Add role management UI in admin panel
- Add role dropdown column in users table - Add /admin/users-api/change-role endpoint - Sync is_admin flag when role changes - Auto-create UserCompanyPermissions for EMPLOYEE - Prevent self-demotion from admin Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
ae70ad326e
commit
a325e1b2e4
@ -17,7 +17,7 @@ from flask import jsonify, request
|
||||
from flask_login import current_user, login_required
|
||||
from werkzeug.security import generate_password_hash
|
||||
|
||||
from database import SessionLocal, User, Company
|
||||
from database import SessionLocal, User, Company, SystemRole, CompanyRole, UserCompanyPermissions
|
||||
import gemini_service
|
||||
from . import bp
|
||||
|
||||
@ -305,3 +305,102 @@ def admin_users_bulk_create():
|
||||
return jsonify({'success': False, 'error': f'Błąd: {str(e)}'}), 500
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
|
||||
@bp.route('/users-api/change-role', methods=['POST'])
|
||||
@login_required
|
||||
def admin_users_change_role():
|
||||
"""Change user's system role."""
|
||||
if not current_user.is_admin:
|
||||
return jsonify({'success': False, 'error': 'Brak uprawnień'}), 403
|
||||
|
||||
db = SessionLocal()
|
||||
try:
|
||||
data = request.get_json() or {}
|
||||
user_id = data.get('user_id')
|
||||
new_role = data.get('role')
|
||||
|
||||
if not user_id or not new_role:
|
||||
return jsonify({'success': False, 'error': 'Brak wymaganych danych'}), 400
|
||||
|
||||
# Validate role
|
||||
valid_roles = ['UNAFFILIATED', 'MEMBER', 'EMPLOYEE', 'MANAGER', 'OFFICE_MANAGER', 'ADMIN']
|
||||
if new_role not in valid_roles:
|
||||
return jsonify({'success': False, 'error': f'Nieprawidłowa rola: {new_role}'}), 400
|
||||
|
||||
# Get user
|
||||
user = db.query(User).filter(User.id == user_id).first()
|
||||
if not user:
|
||||
return jsonify({'success': False, 'error': 'Użytkownik nie znaleziony'}), 404
|
||||
|
||||
# Prevent self-demotion from admin
|
||||
if user.id == current_user.id and new_role != 'ADMIN':
|
||||
return jsonify({'success': False, 'error': 'Nie możesz odebrać sobie uprawnień administratora'}), 400
|
||||
|
||||
old_role = user.role
|
||||
user.role = new_role
|
||||
|
||||
# Sync is_admin flag
|
||||
user.is_admin = (new_role == 'ADMIN')
|
||||
|
||||
# Update company_role based on new role
|
||||
if new_role in ['MANAGER']:
|
||||
user.company_role = 'MANAGER'
|
||||
elif new_role in ['EMPLOYEE']:
|
||||
user.company_role = 'EMPLOYEE'
|
||||
elif new_role in ['UNAFFILIATED', 'MEMBER']:
|
||||
user.company_role = 'NONE'
|
||||
# OFFICE_MANAGER and ADMIN keep their company_role unchanged
|
||||
|
||||
# Create default permissions for EMPLOYEE if they have a company
|
||||
if new_role == 'EMPLOYEE' and user.company_id:
|
||||
existing_perms = db.query(UserCompanyPermissions).filter_by(
|
||||
user_id=user.id,
|
||||
company_id=user.company_id
|
||||
).first()
|
||||
|
||||
if not existing_perms:
|
||||
perms = UserCompanyPermissions(
|
||||
user_id=user.id,
|
||||
company_id=user.company_id,
|
||||
granted_by_id=current_user.id
|
||||
)
|
||||
db.add(perms)
|
||||
|
||||
db.commit()
|
||||
|
||||
logger.info(f"Admin {current_user.email} changed role for user {user.email}: {old_role} -> {new_role}")
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'message': f'Rola zmieniona na {new_role}',
|
||||
'user_id': user.id,
|
||||
'old_role': old_role,
|
||||
'new_role': new_role
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
db.rollback()
|
||||
logger.error(f"Error changing user role: {e}")
|
||||
return jsonify({'success': False, 'error': f'Błąd: {str(e)}'}), 500
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
|
||||
@bp.route('/users-api/roles', methods=['GET'])
|
||||
@login_required
|
||||
def admin_users_get_roles():
|
||||
"""Get list of available roles for dropdown."""
|
||||
if not current_user.is_admin:
|
||||
return jsonify({'success': False, 'error': 'Brak uprawnień'}), 403
|
||||
|
||||
roles = [
|
||||
{'value': 'UNAFFILIATED', 'label': 'Niezrzeszony', 'description': 'Firma spoza Izby'},
|
||||
{'value': 'MEMBER', 'label': 'Członek', 'description': 'Członek Norda bez firmy'},
|
||||
{'value': 'EMPLOYEE', 'label': 'Pracownik', 'description': 'Pracownik firmy członkowskiej'},
|
||||
{'value': 'MANAGER', 'label': 'Kadra Zarządzająca', 'description': 'Pełna kontrola firmy'},
|
||||
{'value': 'OFFICE_MANAGER', 'label': 'Kierownik Biura', 'description': 'Panel admina bez użytkowników'},
|
||||
{'value': 'ADMIN', 'label': 'Administrator', 'description': 'Pełne prawa'},
|
||||
]
|
||||
|
||||
return jsonify({'success': True, 'roles': roles})
|
||||
|
||||
@ -181,6 +181,29 @@
|
||||
color: #1D4ED8;
|
||||
}
|
||||
|
||||
.role-select {
|
||||
padding: 4px 8px;
|
||||
font-size: var(--font-size-sm);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius);
|
||||
background: var(--surface);
|
||||
cursor: pointer;
|
||||
min-width: 140px;
|
||||
}
|
||||
|
||||
.role-select:hover:not(:disabled) {
|
||||
border-color: var(--primary);
|
||||
}
|
||||
|
||||
.role-select:disabled {
|
||||
opacity: 0.6;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.role-select option {
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
.badge-verified {
|
||||
background: #D1FAE5;
|
||||
color: #065F46;
|
||||
@ -1085,6 +1108,7 @@
|
||||
<th>ID</th>
|
||||
<th>Użytkownik</th>
|
||||
<th>Firma</th>
|
||||
<th>Rola</th>
|
||||
<th>Utworzono</th>
|
||||
<th>Status</th>
|
||||
<th>Akcje</th>
|
||||
@ -1112,6 +1136,19 @@
|
||||
<span style="color: var(--text-secondary);">-</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
<select class="role-select"
|
||||
data-user-id="{{ user.id }}"
|
||||
onchange="changeUserRole({{ user.id }}, this.value)"
|
||||
{% if user.id == current_user.id %}disabled title="Nie możesz zmienić swojej roli"{% endif %}>
|
||||
<option value="UNAFFILIATED" {% if user.role == 'UNAFFILIATED' %}selected{% endif %}>Niezrzeszony</option>
|
||||
<option value="MEMBER" {% if user.role == 'MEMBER' %}selected{% endif %}>Członek</option>
|
||||
<option value="EMPLOYEE" {% if user.role == 'EMPLOYEE' %}selected{% endif %}>Pracownik</option>
|
||||
<option value="MANAGER" {% if user.role == 'MANAGER' %}selected{% endif %}>Kadra Zarządzająca</option>
|
||||
<option value="OFFICE_MANAGER" {% if user.role == 'OFFICE_MANAGER' %}selected{% endif %}>Kierownik Biura</option>
|
||||
<option value="ADMIN" {% if user.role == 'ADMIN' %}selected{% endif %}>Administrator</option>
|
||||
</select>
|
||||
</td>
|
||||
<td style="font-size: var(--font-size-sm); color: var(--text-secondary);">
|
||||
{{ user.created_at.strftime('%d.%m.%Y %H:%M') }}
|
||||
</td>
|
||||
@ -1701,6 +1738,60 @@ Lub format CSV, Excel, lista emaili..."></textarea>
|
||||
});
|
||||
});
|
||||
|
||||
async function changeUserRole(userId, newRole) {
|
||||
const select = document.querySelector(`select.role-select[data-user-id="${userId}"]`);
|
||||
const originalValue = select.dataset.originalValue || select.value;
|
||||
|
||||
try {
|
||||
const response = await fetch('/admin/users-api/change-role', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRFToken': csrfToken
|
||||
},
|
||||
body: JSON.stringify({
|
||||
user_id: userId,
|
||||
role: newRole
|
||||
})
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
if (data.success) {
|
||||
select.dataset.originalValue = newRole;
|
||||
showMessage(`Rola zmieniona na: ${getRoleLabel(newRole)}`, 'success');
|
||||
|
||||
// Update admin toggle button state if role changed to/from ADMIN
|
||||
const row = select.closest('tr');
|
||||
const adminBtn = row.querySelector('.admin-toggle');
|
||||
if (adminBtn) {
|
||||
if (newRole === 'ADMIN') {
|
||||
adminBtn.classList.add('active');
|
||||
} else {
|
||||
adminBtn.classList.remove('active');
|
||||
}
|
||||
}
|
||||
} else {
|
||||
select.value = originalValue;
|
||||
showMessage(data.error || 'Wystąpił błąd', 'error');
|
||||
}
|
||||
} catch (error) {
|
||||
select.value = originalValue;
|
||||
showMessage('Błąd połączenia', 'error');
|
||||
}
|
||||
}
|
||||
|
||||
function getRoleLabel(role) {
|
||||
const labels = {
|
||||
'UNAFFILIATED': 'Niezrzeszony',
|
||||
'MEMBER': 'Członek',
|
||||
'EMPLOYEE': 'Pracownik',
|
||||
'MANAGER': 'Kadra Zarządzająca',
|
||||
'OFFICE_MANAGER': 'Kierownik Biura',
|
||||
'ADMIN': 'Administrator'
|
||||
};
|
||||
return labels[role] || role;
|
||||
}
|
||||
|
||||
async function toggleAdmin(userId) {
|
||||
try {
|
||||
const response = await fetch(`/admin/users/${userId}/toggle-admin`, {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user