1{% extends "base_main_template.html" %}
3{% block additional_scripts_css %}
6 background-color: #ffffff !important;
7 color: #333333 !important;
8 border: 2px solid #e9ecef;
9 box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
13 border-bottom: 2px solid #e9ecef;
17 background-color: #ffffff !important;
18 color: #333333 !important;
22 background-color: #f8f9fa !important;
23 color: #495057 !important;
24 border-color: #dee2e6 !important;
28 border-color: #dee2e6 !important;
29 color: #333333 !important;
32 .form-control, .form-select {
33 background-color: #ffffff !important;
34 color: #333333 !important;
35 border: 2px solid #ced4da !important;
38 .form-control:focus, .form-select:focus {
39 border-color: #80bdff !important;
40 box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25) !important;
49 border: 2px solid #e9ecef;
50 background-color: #ffffff !important;
51 color: #333333 !important;
55 color: #6c757d !important;
58 h1, h2, h3, h4, h5, h6, p, label {
59 color: #333333 !important;
70<div class="container mt-4">
74 <div class="card-header bg-primary text-white">
76 <i class="fas fa-users me-2"></i>
77 Manage Members - {{ project.name }}
80 <div class="card-body">
81 <div class="row mb-4">
82 <div class="col-md-6">
83 <h5 class="text-dark">Project Information</h5>
84 <p class="text-dark"><strong>Name:</strong> {{ project.name }}</p>
85 <p class="text-dark"><strong>Description:</strong> {{ project.description or 'No description' }}</p>
86 <p class="text-dark"><strong>Created:</strong> {{ project.creation_date.strftime('%Y-%m-%d %H:%M:%S') if project.creation_date else 'Unknown' }}</p>
88 <div class="col-md-6">
89 <a href="{{ url_for('project') }}" class="btn btn-secondary">
90 <i class="fas fa-arrow-left me-2"></i>Back to Projects
95 <!-- Current Members Section -->
96 <div class="card mb-4">
97 <div class="card-header bg-info text-white">
99 <i class="fas fa-user-check me-2"></i>
100 Current Members ({{ current_members|length }})
103 <div class="card-body">
104 {% if current_members %}
105 <div class="table-responsive">
106 <table class="table table-striped">
107 <thead class="table-dark">
116 {% for member in current_members %}
119 <strong>{{ member.user.name }}</strong>
120 {% if member.user.is_admin %}
121 <span class="badge bg-warning text-dark ms-2">System Admin</span>
124 <td>{{ member.user.mail or 'No email' }}</td>
126 {% if member.role_type == 'owner' %}
127 <span class="badge bg-danger">Owner</span>
128 {% elif member.role_type == 'admin' %}
129 <span class="badge bg-warning text-dark">Admin</span>
130 {% elif member.role_type == 'editor' %}
131 <span class="badge bg-primary">Editor</span>
132 {% elif member.role_type == 'viewer' %}
133 <span class="badge bg-info">Viewer</span>
134 {% elif member.role_type == 'guest' %}
135 <span class="badge bg-secondary">Guest</span>
139 {% if member.role_type != 'owner' %}
140 <!-- Change Role Dropdown -->
141 <div class="dropdown d-inline-block me-2">
142 <button class="btn btn-sm btn-outline-primary dropdown-toggle" type="button" data-bs-toggle="dropdown">
145 <ul class="dropdown-menu">
146 <li><a class="dropdown-item" href="#" onclick="changeRole({{ member.user_id }}, 'admin')">Admin</a></li>
147 <li><a class="dropdown-item" href="#" onclick="changeRole({{ member.user_id }}, 'editor')">Editor</a></li>
148 <li><a class="dropdown-item" href="#" onclick="changeRole({{ member.user_id }}, 'viewer')">Viewer</a></li>
149 <li><a class="dropdown-item" href="#" onclick="changeRole({{ member.user_id }}, 'guest')">Guest</a></li>
153 <!-- Remove Member Button -->
154 <form method="POST" class="d-inline-block" onsubmit="return confirm('Are you sure you want to remove this member?')">
155 <input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
158 <input type="hidden" name="remove_member" value="1">
159 <input type="hidden" name="member_id" value="{{ member.user_id }}">
160 <button type="submit" class="btn btn-sm btn-outline-danger">
161 <i class="fas fa-user-minus me-1"></i>Remove
165 <!-- Owner Actions -->
166 <div class="dropdown d-inline-block">
167 <button class="btn btn-sm btn-outline-warning dropdown-toggle" type="button" data-bs-toggle="dropdown">
168 <i class="fas fa-crown me-1"></i>Owner Actions
170 <ul class="dropdown-menu">
171 <li><a class="dropdown-item" href="#" onclick="showTransferOwnershipModal({{ member.user_id }}, '{{ member.user.name }}')">
172 <i class="fas fa-exchange-alt me-2"></i>Transfer Ownership
184 <div class="alert alert-info">
185 <i class="fas fa-info-circle me-2"></i>
186 No members found for this project.
192 <!-- Ownership Management Section -->
193 <div class="card mb-4">
194 <div class="card-header bg-warning text-dark">
196 <i class="fas fa-crown me-2"></i>
200 <div class="card-body">
202 <div class="col-md-8">
203 <h6><i class="fas fa-info-circle me-2"></i>Current Ownership Status</h6>
204 <p class="mb-2">This section allows project owners to transfer ownership to other members.</p>
205 <div class="alert alert-info">
206 <strong>Note:</strong> Only project owners can transfer ownership. The transfer is permanent and cannot be undone.
209 <div class="col-md-4 text-end">
210 <button class="btn btn-warning" onclick="showOwnershipInfo()">
211 <i class="fas fa-question-circle me-2"></i>Learn More
218 <!-- Add New Member Section -->
220 <div class="card-header bg-success text-white">
222 <i class="fas fa-user-plus me-2"></i>
226 <div class="card-body">
227 {% if available_users %}
229 <input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
232 <div class="col-md-5">
233 <label for="user_id" class="form-label">Select User</label>
234 <select class="form-select" id="user_id" name="user_id" required>
235 <option value="">Choose a user...</option>
236 {% for user in available_users %}
237 <option value="{{ user.id }}">
238 {{ user.name }} {% if user.mail %}({{ user.mail }}){% endif %}
243 <div class="col-md-5">
244 <label for="role_type" class="form-label">Assign Role</label>
245 <select class="form-select" id="role_type" name="role_type" required>
246 <option value="">Choose a role...</option>
247 <option value="admin">Admin - Full project management</option>
248 <option value="editor">Editor - Can modify content</option>
249 <option value="viewer">Viewer - Read-only access</option>
250 <option value="guest">Guest - Limited access</option>
253 <div class="col-md-2">
254 <label class="form-label"> </label>
255 <button type="submit" name="add_member" class="btn btn-success w-100">
256 <i class="fas fa-plus me-2"></i>Add
263 <h6>Role Descriptions:</h6>
265 <div class="col-md-3">
266 <span class="badge bg-danger">Owner</span>
267 <small class="d-block text-muted">Full control, can transfer ownership</small>
269 <div class="col-md-3">
270 <span class="badge bg-warning text-dark">Admin</span>
271 <small class="d-block text-muted">Manage members and settings</small>
273 <div class="col-md-3">
274 <span class="badge bg-primary">Editor</span>
275 <small class="d-block text-muted">Create and modify content</small>
277 <div class="col-md-3">
278 <span class="badge bg-info">Viewer</span>
279 <small class="d-block text-muted">Read-only access</small>
285 <div class="alert alert-warning border-warning">
286 <h5><i class="fas fa-crown me-2"></i>Ownership Management</h5>
288 <div class="col-md-6">
289 <h6><i class="fas fa-shield-alt me-2"></i>Ownership Rules:</h6>
291 <li><strong>Minimum Owner:</strong> Each project must have at least one owner</li>
292 <li><strong>Transfer Rights:</strong> Only owners can transfer ownership to other members</li>
293 <li><strong>Last Owner Protection:</strong> The last owner cannot be removed from the project</li>
294 <li><strong>Irreversible:</strong> Ownership transfer is permanent and cannot be undone</li>
297 <div class="col-md-6">
298 <h6><i class="fas fa-exclamation-triangle me-2"></i>Important Notes:</h6>
300 <li>After transferring ownership, you will become an admin</li>
301 <li>Only existing project members can receive ownership</li>
302 <li>Ownership transfer requires explicit confirmation</li>
303 <li>All ownership changes are logged for audit purposes</li>
310 <div class="alert alert-warning">
311 <i class="fas fa-exclamation-triangle me-2"></i>
312 All users are already members of this project.
323<!-- Transfer Ownership Modal -->
324<div class="modal fade" id="transferOwnershipModal" tabindex="-1" aria-labelledby="transferOwnershipModalLabel" aria-hidden="true">
325 <div class="modal-dialog modal-lg">
326 <div class="modal-content">
327 <div class="modal-header bg-danger text-white">
328 <h4 class="modal-title" id="transferOwnershipModalLabel">
329 <i class="fas fa-crown me-2"></i>Transfer Project Ownership
331 <button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Close"></button>
333 <div class="modal-body">
334 <div class="alert alert-danger border-danger">
335 <h5><i class="fas fa-exclamation-triangle me-2"></i>Critical Action Warning</h5>
336 <p class="mb-0"><strong>This action will permanently transfer full ownership of the project to another member.</strong> You will become an admin after the transfer and will lose all owner privileges.</p>
339 <div class="row mb-3">
340 <div class="col-md-6">
341 <div class="card border-primary">
342 <div class="card-header bg-primary text-white">
343 <h6 class="mb-0"><i class="fas fa-user me-2"></i>Current Owner</h6>
345 <div class="card-body">
346 <p class="mb-0"><strong id="currentOwnerName"></strong></p>
347 <small class="text-muted">Will become Admin after transfer</small>
351 <div class="col-md-6">
352 <div class="card border-success">
353 <div class="card-header bg-success text-white">
354 <h6 class="mb-0"><i class="fas fa-user-plus me-2"></i>New Owner</h6>
356 <div class="card-body">
357 <form id="transferOwnershipForm">
358 <select class="form-select" id="newOwnerSelect" required>
359 <option value="">Choose a member...</option>
360 {% for member in current_members %}
361 {% if member.role_type != 'owner' %}
362 <option value="{{ member.user_id }}">
363 {{ member.user.name }} ({{ member.role_type|title }})
374 <div class="alert alert-warning">
375 <h6><i class="fas fa-info-circle me-2"></i>What happens after transfer:</h6>
377 <li>You will lose all owner privileges and become an admin</li>
378 <li>The new owner will have full control over the project</li>
379 <li>This action cannot be undone</li>
380 <li>All project settings and permissions will be transferred</li>
385 <div class="form-check">
386 <input class="form-check-input" type="checkbox" id="confirmTransfer" required>
387 <label class="form-check-label" for="confirmTransfer">
388 <strong>I understand the consequences and confirm that I want to transfer ownership</strong>
393 <div class="modal-footer">
394 <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">
395 <i class="fas fa-times me-2"></i>Cancel
397 <button type="button" class="btn btn-danger" id="confirmTransferBtn" disabled>
398 <i class="fas fa-exchange-alt me-2"></i>Transfer Ownership
405<!-- Hidden form for role changes -->
406<form id="roleChangeForm" method="POST" style="display: none;">
407 <input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
409 <input type="hidden" name="change_role" value="1">
410 <input type="hidden" name="member_id" id="changeRoleMemberId">
411 <input type="hidden" name="new_role" id="changeRoleNewRole">
414<!-- Hidden form for ownership transfer -->
415<form id="ownershipTransferForm" method="POST" style="display: none;">
416 <input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
417 <input type="hidden" name="transfer_ownership" value="1">
418 <input type="hidden" name="new_owner_id" id="newOwnerId">
422function changeRole(userId, newRole) {
423 if (confirm(`Are you sure you want to change this user's role to ${newRole}?`)) {
424 document.getElementById('changeRoleMemberId').value = userId;
425 document.getElementById('changeRoleNewRole').value = newRole;
426 document.getElementById('roleChangeForm').submit();
430function showTransferOwnershipModal(currentOwnerId, currentOwnerName) {
431 // Set the current owner name in the modal
432 document.getElementById('currentOwnerName').textContent = currentOwnerName;
435 document.getElementById('newOwnerSelect').value = '';
436 document.getElementById('confirmTransfer').checked = false;
437 document.getElementById('confirmTransferBtn').disabled = true;
440 var modal = new bootstrap.Modal(document.getElementById('transferOwnershipModal'));
444function showOwnershipInfo() {
445 alert('Ownership Transfer Information:\n\n' +
446 '• Only project owners can transfer ownership\n' +
447 '• The new owner must already be a project member\n' +
448 '• After transfer, you will become an admin\n' +
449 '• This action is permanent and cannot be undone\n' +
450 '• All project permissions will be transferred\n\n' +
451 'To transfer ownership, use the "Owner Actions" button in the members table.');
454// Enable/disable transfer button based on form completion
455document.addEventListener('DOMContentLoaded', function() {
456 const newOwnerSelect = document.getElementById('newOwnerSelect');
457 const confirmTransfer = document.getElementById('confirmTransfer');
458 const confirmTransferBtn = document.getElementById('confirmTransferBtn');
460 function updateTransferButton() {
461 const isFormValid = newOwnerSelect.value && confirmTransfer.checked;
462 confirmTransferBtn.disabled = !isFormValid;
465 newOwnerSelect.addEventListener('change', updateTransferButton);
466 confirmTransfer.addEventListener('change', updateTransferButton);
468 // Handle transfer confirmation
469 confirmTransferBtn.addEventListener('click', function() {
470 const newOwnerId = newOwnerSelect.value;
471 const newOwnerName = newOwnerSelect.options[newOwnerSelect.selectedIndex].text;
473 if (confirm(`Are you absolutely sure you want to transfer ownership to ${newOwnerName}?\n\nThis action cannot be undone.`)) {
474 document.getElementById('newOwnerId').value = newOwnerId;
475 document.getElementById('ownershipTransferForm').submit();
479 // Check ownership constraints on page load
480 checkOwnershipConstraints();
483// Function to check ownership constraints via API
484function checkOwnershipConstraints() {
485 const projectId = {{ project.id }};
487 fetch(`/api/project/${projectId}/ownership-constraints`)
488 .then(response => response.json())
491 console.error('Error checking ownership constraints:', data.error);
495 // Update UI based on constraints
496 updateUIForConstraints(data);
499 console.error('Error:', error);
503function updateUIForConstraints(constraints) {
504 // Hide transfer ownership options if user cannot transfer
505 if (!constraints.can_transfer_ownership) {
506 const transferButtons = document.querySelectorAll('[onclick*="showTransferOwnershipModal"]');
507 transferButtons.forEach(button => {
508 button.style.display = 'none';
512 // Show warning if this is the last owner
513 if (constraints.is_last_owner) {
514 const ownerRows = document.querySelectorAll('tr');
515 ownerRows.forEach(row => {
516 const roleCell = row.querySelector('.badge.bg-danger');
517 if (roleCell && roleCell.textContent.trim() === 'Owner') {
518 const actionsCell = row.querySelector('td:last-child');
520 const warning = document.createElement('div');
521 warning.className = 'alert alert-warning alert-sm mt-2';
522 warning.innerHTML = '<i class="fas fa-exclamation-triangle me-1"></i>Last owner - cannot be removed';
523 actionsCell.appendChild(warning);