User management and module improvements

- Added daily_mirror module to permissions system
- Fixed user module management - updates now work correctly
- Implemented dashboard module filtering based on user permissions
- Fixed warehouse create_locations page (config parser and delete)
- Implemented POST-Redirect-GET pattern to prevent duplicate entries
- Added application license system with validation middleware
- Cleaned up debug logging code
- Improved user module selection with fetch API instead of form submit
This commit is contained in:
ske087
2025-11-29 14:16:36 +02:00
parent 3e314332a7
commit 7912885046
9 changed files with 355 additions and 69 deletions

View File

@@ -702,35 +702,6 @@ function confirmDelete() {
});
}
// Handle delete confirmation
function confirmDelete() {
const locationId = document.getElementById('delete-confirm-id').textContent;
// Send delete request
fetch('/delete_location', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ location_id: locationId })
})
.then(response => response.json())
.then(result => {
if (result.success) {
showNotification('✅ Location deleted successfully!', 'success');
closeDeleteModal();
// Reload page to show changes
setTimeout(() => window.location.reload(), 1000);
} else {
showNotification('❌ Error deleting location: ' + result.error, 'error');
}
})
.catch(error => {
console.error('Error:', error);
showNotification('❌ Error deleting location: ' + error.message, 'error');
});
}
// Close modals when clicking outside
window.addEventListener('click', function(event) {
const editModal = document.getElementById('edit-modal');

View File

@@ -12,6 +12,7 @@
<div class="dashboard-container">
<!-- Row of evenly distributed cards -->
{% if 'quality' in user_modules %}
<div class="dashboard-card">
<h3>Quality Module</h3>
<p>Final scanning module for production orders and quality reports access.</p>
@@ -20,26 +21,34 @@
<a href="{{ url_for('main.reports') }}" class="btn">Access to Quality reports</a>
</div>
</div>
{% endif %}
{% if 'warehouse' in user_modules %}
<div class="dashboard-card">
<h3>Access Warehouse Module</h3>
<p>Access warehouse module functionalities.</p>
<a href="{{ url_for('main.warehouse') }}" class="btn" id="launch-warehouse">Open Warehouse</a>
</div>
{% endif %}
{% if 'labels' in user_modules %}
<!-- New Card: Access Labels Module -->
<div class="dashboard-card">
<h3>Access Labels Module</h3>
<p>Module for label management.</p>
<a href="{{ url_for('main.etichete') }}" class="btn">Launch Labels Module</a>
</div>
{% endif %}
{% if user_role in ['superadmin', 'admin'] %}
<div class="dashboard-card">
<h3>Manage Settings</h3>
<p>Access and manage application settings.</p>
<a href="{{ url_for('main.settings') }}" class="btn">Access Settings Page</a>
</div>
{% endif %}
{% if 'daily_mirror' in user_modules %}
<div class="dashboard-card">
<h3>📊 Daily Mirror</h3>
<p>Business Intelligence and Production Reporting - Generate comprehensive daily reports including order quantities, production status, and delivery tracking.</p>
@@ -50,6 +59,7 @@
<strong>Tracks:</strong> Orders quantity • Production launched • Production finished • Orders delivered
</div>
</div>
{% endif %}
</div>
</div>

View File

@@ -304,6 +304,81 @@
</table>
</div>
<div class="qz-pairing-card" style="margin-top: 40px;">
<h2>🔐 Application License Management</h2>
{% if license_data %}
<div class="qz-result" style="{% if license_data.days_remaining <= 30 %}background: #fff3e0; border-left-color: #ff9800;{% elif license_data.days_remaining <= 7 %}background: #ffebee; border-left-color: #f44336;{% endif %}">
<div style="margin-bottom: 8px;">
<strong>🔑 License Key:</strong> <span>{{ license_data.license_key }}</span>
</div>
<div style="margin-bottom: 8px;">
<strong>📅 Created:</strong> {{ license_data.created_at }}
</div>
<div style="margin-bottom: 8px;">
<strong>⏰ Valid Until:</strong> {{ license_data.valid_until }}
{% if license_data.days_remaining is defined %}
{% if license_data.days_remaining > 0 %}
<span style="margin-left: 8px; padding: 2px 8px; background: {% if license_data.days_remaining <= 7 %}#f44336{% elif license_data.days_remaining <= 30 %}#ff9800{% else %}#4caf50{% endif %}; color: white; border-radius: 4px; font-size: 0.85em;">
{{ license_data.days_remaining }} days remaining
</span>
{% else %}
<span style="margin-left: 8px; padding: 2px 8px; background: #f44336; color: white; border-radius: 4px; font-size: 0.85em;">
⚠️ EXPIRED
</span>
{% endif %}
{% endif %}
</div>
<div style="margin-top: 16px;">
<form method="POST" action="/revoke_app_license" style="display: inline-block;">
<button class="btn-delete" type="submit" onclick="return confirm('Are you sure you want to REVOKE the application license?\n\nThis will prevent all non-superadmin users from accessing the application!\n\nThis action cannot be undone.');">
🗑️ Revoke License
</button>
</form>
</div>
</div>
{% else %}
<div style="padding: 24px; background: #ffebee; border-left: 4px solid #f44336; border-radius: 6px; margin-bottom: 24px;">
<strong style="color: #c62828;">⚠️ No Active License</strong>
<p style="margin: 8px 0 0 0; color: #d32f2f;">
No application license has been generated. Non-superadmin users will not be able to access the application.
</p>
</div>
{% endif %}
<h3>🔑 Generate New License</h3>
<form id="license-form" method="POST" action="/generate_app_license" class="qz-form-group">
<div class="form-row">
<div class="form-field">
<label for="license_validity_days">License Validity Period:</label>
<select id="license_validity_days" name="validity_days">
<option value="30">30 Days (1 Month)</option>
<option value="90">90 Days (3 Months)</option>
<option value="180">180 Days (6 Months)</option>
<option value="365" selected>365 Days (1 Year)</option>
<option value="730">730 Days (2 Years)</option>
<option value="1825">1825 Days (5 Years)</option>
<option value="3650">3650 Days (10 Years)</option>
</select>
</div>
<button type="submit" onclick="return confirm('{% if license_data %}This will replace the existing license key.\n\n{% endif %}Are you sure you want to generate a new application license?');">
🔑 Generate License Key
</button>
</div>
</form>
<div style="margin-top: 24px; padding: 16px; background: var(--info-bg, #e3f2fd); border-left: 4px solid #2196f3; border-radius: 6px;">
<strong style="color: var(--info-text, #0d47a1);"> License Information</strong>
<ul style="margin: 8px 0 0 0; color: var(--info-text, #1565c0); font-size: 0.9em;">
<li>The application license controls access for all non-superadmin users</li>
<li>Superadmin accounts can always access the application regardless of license status</li>
<li>When the license expires, non-superadmin users will see an expiration message</li>
<li>Generating a new license will replace any existing license</li>
</ul>
</div>
</div>
<script>
function deletePairingKey(pairingKey, printerName) {
if (confirm(`Are you sure you want to delete the pairing key for "${printerName}"?\n\nKey: ${pairingKey}\n\nThis action cannot be undone.`)) {

View File

@@ -51,6 +51,22 @@
</small>
</div>
{% if session.role == 'superadmin' %}
<div class="card" style="margin-top: 32px;">
<h3>🖨️ Print Extension Management</h3>
<p><strong>QZ Tray Pairing Keys:</strong> Manage printer connection keys</p>
<p>Control access to direct printing functionality for label modules</p>
<div style="margin-top: 15px;">
<a href="{{ url_for('main.download_extension') }}" class="btn" style="background-color: #4caf50; color: white;">
🔑 Manage Pairing Keys
</a>
</div>
<small style="display: block; margin-top: 10px; color: #666;">
Generate and manage pairing keys for QZ Tray printer integration
</small>
</div>
{% endif %}
{% if session.role in ['superadmin', 'admin'] %}
<div class="card backup-card" style="margin-top: 32px;">
<h3>💾 Database Backup Management</h3>

View File

@@ -597,7 +597,7 @@
</thead>
<tbody>
{% for user in users %}
<tr class="user-row" data-user-id="{{ user.id }}" data-username="{{ user.username }}" data-role="{{ user.role }}" data-modules="{{ (user.get_modules() or []) | tojson }}">
<tr class="user-row" data-user-id="{{ user.id }}" data-username="{{ user.username }}" data-role="{{ user.role }}" data-modules='{{ (user.get_modules() or []) | tojson }}'>
<td>
<strong>{{ user.username }}</strong>
</td>
@@ -638,7 +638,7 @@
data-user-id="{{ user.id }}"
data-username="{{ user.username }}"
data-role="{{ user.role }}"
data-modules="{{ (user.get_modules() or []) | tojson }}"
data-modules='{{ (user.get_modules() or []) | tojson }}'
title="Select for quick edit">
📝 Select
</button>
@@ -646,7 +646,7 @@
data-user-id="{{ user.id }}"
data-username="{{ user.username }}"
data-role="{{ user.role }}"
data-modules="{{ (user.get_modules() or []) | tojson }}"
data-modules='{{ (user.get_modules() or []) | tojson }}'
title="Full edit">
⚙️ Edit
</button>
@@ -798,6 +798,8 @@ function updateEditModuleSelection() {
let selectedUser = null;
function selectUserForQuickEdit(userId, username, role, modules) {
console.log('Selecting user:', {userId, username, role, modules});
selectedUser = {id: userId, username: username, role: role, modules: modules};
// Update the quick edit panel
@@ -806,25 +808,49 @@ function selectUserForQuickEdit(userId, username, role, modules) {
// Clear all module checkboxes first
const checkboxes = document.querySelectorAll('#currentModules input[type="checkbox"]');
checkboxes.forEach(cb => cb.checked = false);
checkboxes.forEach(cb => {
cb.checked = false;
console.log('Cleared checkbox:', cb.id);
});
// Check the appropriate modules
if (modules && Array.isArray(modules)) {
if (modules && Array.isArray(modules) && modules.length > 0) {
console.log('User has modules:', modules);
modules.forEach(module => {
const checkbox = document.getElementById('quick_module_' + module);
if (checkbox) checkbox.checked = true;
console.log('Looking for checkbox:', 'quick_module_' + module, 'Found:', checkbox);
if (checkbox) {
checkbox.checked = true;
console.log('Checked module:', module);
}
});
} else {
console.log('No modules found for user or empty array');
}
// Disable checkboxes for superadmin and admin roles
if (role === 'superadmin' || role === 'admin') {
checkboxes.forEach(cb => {
cb.disabled = true;
cb.checked = false;
});
} else {
checkboxes.forEach(cb => {
cb.disabled = false;
});
}
// Show the quick edit panel
document.getElementById('quickModuleEdit').style.display = 'block';
document.querySelector('#userRightsPanel .text-center').style.display = 'none';
const emptyMessage = document.querySelector('#userRightsPanel .text-center');
if (emptyMessage) emptyMessage.style.display = 'none';
// Highlight selected row
document.querySelectorAll('.user-row').forEach(row => {
row.classList.remove('table-warning');
});
document.querySelector(`[data-user-id="${userId}"]`).classList.add('table-warning');
const selectedRow = document.querySelector(`[data-user-id="${userId}"]`);
if (selectedRow) selectedRow.classList.add('table-warning');
}
function updateUserModules() {
@@ -833,39 +859,56 @@ function updateUserModules() {
return;
}
console.log('updateUserModules called for user:', selectedUser);
// Get selected modules
const checkboxes = document.querySelectorAll('#currentModules input[type="checkbox"]:checked');
const modules = Array.from(checkboxes).map(cb => cb.value);
console.log('Selected modules:', modules);
console.log('User role:', selectedUser.role);
// Validate modules for the role
if ((selectedUser.role === 'manager' || selectedUser.role === 'worker') && modules.length === 0) {
alert(`${selectedUser.role}s must have at least one module assigned.`);
return;
}
// Create and submit form
const form = document.createElement('form');
form.method = 'POST';
form.action = '/quick_update_modules';
// Show confirmation
const modulesList = modules.length > 0 ? modules.join(', ') : 'None';
if (!confirm(`Update modules for user "${selectedUser.username}"?\n\nNew modules: ${modulesList}`)) {
return;
}
// Add user ID
const userIdInput = document.createElement('input');
userIdInput.type = 'hidden';
userIdInput.name = 'user_id';
userIdInput.value = selectedUser.id;
form.appendChild(userIdInput);
// Add modules
// Use fetch to submit and see the response
const formData = new FormData();
formData.append('user_id', selectedUser.id);
modules.forEach(module => {
const input = document.createElement('input');
input.type = 'hidden';
input.name = 'modules';
input.value = module;
form.appendChild(input);
formData.append('modules', module);
});
document.body.appendChild(form);
form.submit();
console.log('Submitting via fetch to /quick_update_modules');
console.log('FormData entries:', Array.from(formData.entries()));
fetch('/quick_update_modules', {
method: 'POST',
body: formData
})
.then(response => {
console.log('Response status:', response.status);
console.log('Response headers:', Array.from(response.headers.entries()));
return response.text();
})
.then(html => {
console.log('Response HTML length:', html.length);
console.log('Response HTML preview:', html.substring(0, 500));
// Reload the page to show updated data
window.location.reload();
})
.catch(error => {
console.error('Fetch error:', error);
alert('Error updating modules: ' + error.message);
});
}
function showFullEditModal() {
@@ -956,9 +999,12 @@ document.addEventListener('DOMContentLoaded', function() {
const role = this.dataset.role;
let modules = [];
try {
modules = JSON.parse(this.dataset.modules) || [];
const modulesData = this.dataset.modules;
console.log('Raw modules data:', modulesData);
modules = JSON.parse(modulesData) || [];
console.log('Parsed modules:', modules);
} catch (e) {
console.log('Error parsing modules for user:', username, e);
console.error('Error parsing modules for user:', username, e);
modules = [];
}
selectUserForQuickEdit(userId, username, role, modules);