Files
quality_app/py_app/app/templates/settings.html
2025-11-03 21:17:10 +02:00

363 lines
16 KiB
HTML
Executable File
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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{% endblock %}
{% block content %}
<div class="card-container">
<div class="card">
<h3>Manage Users (Legacy)</h3>
<ul class="user-list">
{% for user in users %}
<li data-user-id="{{ user.id }}" data-username="{{ user.username }}" data-email="{{ user.email if user.email else '' }}" data-role="{{ user.role }}">
<span class="user-name">{{ user.username }}</span>
<span class="user-role">Role: {{ user.role }}</span>
<button class="btn edit-user-btn" data-user-id="{{ user.id }}" data-username="{{ user.username }}" data-email="{{ user.email if user.email else '' }}" data-role="{{ user.role }}">Edit User</button>
<button class="btn delete-btn delete-user-btn" data-user-id="{{ user.id }}" data-username="{{ user.username }}">Delete User</button>
</li>
{% endfor %}
</ul>
<button id="create-user-btn" class="btn create-btn">Create User</button>
</div>
<div class="card">
<h3>External Server Settings</h3>
<form method="POST" action="{{ url_for('main.save_external_db') }}" class="form-centered">
<label for="server_domain">Server Domain/IP Address:</label>
<input type="text" id="server_domain" name="server_domain" value="{{ external_settings.get('server_domain', '') }}" required>
<label for="port">Port:</label>
<input type="number" id="port" name="port" value="{{ external_settings.get('port', '') }}" required>
<label for="database_name">Database Name:</label>
<input type="text" id="database_name" name="database_name" value="{{ external_settings.get('database_name', '') }}" required>
<label for="username">Username:</label>
<input type="text" id="username" name="username" value="{{ external_settings.get('username', '') }}" required>
<label for="password">Password:</label>
<input type="password" id="password" name="password" value="{{ external_settings.get('password', '') }}" required>
<button type="submit" class="btn">Save/Update External Database Info Settings</button>
</form>
</div>
<div class="card" style="margin-top: 32px;">
<h3>🎯 User & Permissions Management</h3>
<p><strong>Simplified 4-Tier System:</strong> Superadmin → Admin → Manager → Worker</p>
<p>Streamlined interface with module-based permissions (Quality, Warehouse, Labels)</p>
<div style="margin-top: 15px;">
<a href="{{ url_for('main.user_management_simple') }}" class="btn" style="background-color: #2196f3; color: white; margin-right: 10px;">
🎯 Manage Users (Simplified)
</a>
</div>
<small style="display: block; margin-top: 10px; color: #666;">
Recommended: Use the simplified user management for easier administration
</small>
</div>
{% if session.role in ['superadmin', 'admin'] %}
<div class="card" style="margin-top: 32px;">
<h3>💾 Database Backup Management</h3>
<p><strong>Automated Backup System:</strong> Schedule and manage database backups</p>
<!-- Backup Controls -->
<div style="margin-top: 20px; padding: 15px; background: #f5f5f5; border-radius: 8px;">
<h4 style="margin-top: 0;">Quick Actions</h4>
<button id="backup-now-btn" class="btn" style="background-color: #4caf50; color: white; margin-right: 10px;">
⚡ Backup Now
</button>
<button id="refresh-backups-btn" class="btn" style="background-color: #2196f3; color: white;">
🔄 Refresh List
</button>
</div>
<!-- Schedule Configuration -->
<div style="margin-top: 20px; padding: 15px; background: #f9f9f9; border-radius: 8px;">
<h4 style="margin-top: 0;">Backup Schedule</h4>
<form id="backup-schedule-form" style="display: grid; grid-template-columns: 1fr 1fr; gap: 15px;">
<div>
<label for="schedule-enabled">
<input type="checkbox" id="schedule-enabled" name="enabled"> Enable Scheduled Backups
</label>
</div>
<div>
<label for="schedule-time">Backup Time:</label>
<input type="time" id="schedule-time" name="time" value="02:00">
</div>
<div>
<label for="schedule-frequency">Frequency:</label>
<select id="schedule-frequency" name="frequency">
<option value="daily">Daily</option>
<option value="weekly">Weekly</option>
<option value="monthly">Monthly</option>
</select>
</div>
<div>
<label for="retention-days">Keep backups for (days):</label>
<input type="number" id="retention-days" name="retention_days" value="30" min="1" max="365">
</div>
<div style="grid-column: span 2;">
<button type="submit" class="btn" style="background-color: #ff9800; color: white;">
💾 Save Schedule
</button>
</div>
</form>
</div>
<!-- Backup List -->
<div style="margin-top: 20px;">
<h4>Available Backups</h4>
<div id="backup-list" style="max-height: 400px; overflow-y: auto;">
<p style="text-align: center; color: #999;">Loading backups...</p>
</div>
</div>
<!-- Backup Path Info -->
<div style="margin-top: 15px; padding: 10px; background: #e3f2fd; border-left: 4px solid #2196f3; border-radius: 4px;">
<strong> Backup Location:</strong> <code id="backup-path-display">/srv/quality_app/backups</code>
<br>
<small>Configure backup path in docker-compose.yml (BACKUP_PATH environment variable)</small>
</div>
</div>
{% endif %}
</div>
<!-- Popup for creating/editing a user -->
<div id="user-popup" class="popup" style="display:none; position:fixed; top:0; left:0; width:100vw; height:100vh; background:var(--app-overlay-bg, rgba(30,41,59,0.85)); z-index:9999; align-items:center; justify-content:center;">
<div class="popup-content" style="margin:auto; padding:32px; border-radius:8px; box-shadow:0 2px 8px #333; min-width:320px; max-width:400px; text-align:center;">
<h3 id="user-popup-title">Create/Edit User</h3>
<form id="user-form" method="POST" action="{{ url_for('main.create_user') }}">
<input type="hidden" id="user-id" name="user_id">
<label for="username">Username:</label>
<input type="text" id="username" name="username" required>
<label for="email">Email (Optional):</label>
<input type="email" id="email" name="email">
<label for="password">Password:</label>
<input type="password" id="password" name="password" required>
<label for="role">Role:</label>
<select id="role" name="role" required>
<option value="superadmin">Superadmin</option>
<option value="admin">Admin</option>
<option value="manager">Manager</option>
<option value="warehouse_manager">Warehouse Manager</option>
<option value="warehouse_worker">Warehouse Worker</option>
<option value="quality_manager">Quality Manager</option>
<option value="quality_worker">Quality Worker</option>
</select>
<button type="submit" class="btn">Save</button>
<button type="button" id="close-user-popup-btn" class="btn cancel-btn">Cancel</button>
</form>
</div>
</div>
<!-- Popup for confirming user deletion -->
<div id="delete-user-popup" class="popup">
<div class="popup-content">
<h3>Do you really want to delete the user <span id="delete-username"></span>?</h3>
<form id="delete-user-form" method="POST" action="{{ url_for('main.delete_user') }}">
<input type="hidden" id="delete-user-id" name="user_id">
<button type="submit" class="btn delete-confirm-btn">Yes</button>
<button type="button" id="close-delete-popup-btn" class="btn cancel-btn">No</button>
</form>
</div>
</div>
<script>
document.getElementById('create-user-btn').onclick = function() {
document.getElementById('user-popup').style.display = 'flex';
document.getElementById('user-popup-title').innerText = 'Create User';
document.getElementById('user-form').reset();
document.getElementById('user-form').setAttribute('action', '{{ url_for("main.create_user") }}');
document.getElementById('user-id').value = '';
document.getElementById('password').required = true;
document.getElementById('password').placeholder = '';
document.getElementById('username').readOnly = false;
};
document.getElementById('close-user-popup-btn').onclick = function() {
document.getElementById('user-popup').style.display = 'none';
};
// Edit User button logic
Array.from(document.getElementsByClassName('edit-user-btn')).forEach(function(btn) {
btn.onclick = function() {
document.getElementById('user-popup').style.display = 'flex';
document.getElementById('user-popup-title').innerText = 'Edit User';
document.getElementById('user-id').value = btn.getAttribute('data-user-id');
document.getElementById('username').value = btn.getAttribute('data-username');
document.getElementById('email').value = btn.getAttribute('data-email') || '';
document.getElementById('role').value = btn.getAttribute('data-role');
document.getElementById('password').value = '';
document.getElementById('password').required = false;
document.getElementById('password').placeholder = 'Leave blank to keep current password';
document.getElementById('username').readOnly = true;
document.getElementById('user-form').setAttribute('action', '{{ url_for("main.edit_user") }}');
};
});
// Delete User button logic
Array.from(document.getElementsByClassName('delete-user-btn')).forEach(function(btn) {
btn.onclick = function() {
document.getElementById('delete-user-popup').style.display = 'flex';
document.getElementById('delete-username').innerText = btn.getAttribute('data-username');
document.getElementById('delete-user-id').value = btn.getAttribute('data-user-id');
};
});
document.getElementById('close-delete-popup-btn').onclick = function() {
document.getElementById('delete-user-popup').style.display = 'none';
};
// ========================================
// Database Backup Management Functions
// ========================================
// Load backup schedule on page load
function loadBackupSchedule() {
fetch('/api/backup/schedule')
.then(response => response.json())
.then(data => {
if (data.success) {
document.getElementById('schedule-enabled').checked = data.schedule.enabled;
document.getElementById('schedule-time').value = data.schedule.time;
document.getElementById('schedule-frequency').value = data.schedule.frequency;
document.getElementById('retention-days').value = data.schedule.retention_days;
}
})
.catch(error => console.error('Error loading schedule:', error));
}
// Load backup list
function loadBackupList() {
const backupList = document.getElementById('backup-list');
if (!backupList) return;
backupList.innerHTML = '<p style="text-align: center; color: #999;">Loading backups...</p>';
fetch('/api/backup/list')
.then(response => response.json())
.then(data => {
if (data.success && data.backups.length > 0) {
let html = '<table style="width: 100%; border-collapse: collapse;">';
html += '<thead><tr style="background: #f0f0f0;"><th style="padding: 10px; text-align: left;">Filename</th><th>Size</th><th>Created</th><th>Actions</th></tr></thead>';
html += '<tbody>';
data.backups.forEach(backup => {
html += `<tr style="border-bottom: 1px solid #ddd;">
<td style="padding: 10px;">${backup.filename}</td>
<td style="text-align: center;">${backup.size_mb} MB</td>
<td style="text-align: center;">${backup.created}</td>
<td style="text-align: center;">
<button onclick="downloadBackup('${backup.filename}')" class="btn" style="background: #2196f3; color: white; padding: 5px 10px; margin: 2px;">⬇️ Download</button>
<button onclick="deleteBackup('${backup.filename}')" class="btn" style="background: #f44336; color: white; padding: 5px 10px; margin: 2px;">🗑️ Delete</button>
</td>
</tr>`;
});
html += '</tbody></table>';
backupList.innerHTML = html;
} else {
backupList.innerHTML = '<p style="text-align: center; color: #999;">No backups available</p>';
}
})
.catch(error => {
console.error('Error loading backups:', error);
backupList.innerHTML = '<p style="text-align: center; color: #f44336;">Error loading backups</p>';
});
}
// Backup now button
document.getElementById('backup-now-btn')?.addEventListener('click', function() {
const btn = this;
btn.disabled = true;
btn.innerHTML = '⏳ Creating backup...';
fetch('/api/backup/create', {
method: 'POST'
})
.then(response => response.json())
.then(data => {
if (data.success) {
alert('✅ ' + data.message + '\nFile: ' + data.filename + '\nSize: ' + data.size);
loadBackupList();
} else {
alert('❌ ' + data.message);
}
btn.disabled = false;
btn.innerHTML = '⚡ Backup Now';
})
.catch(error => {
console.error('Error creating backup:', error);
alert('❌ Failed to create backup');
btn.disabled = false;
btn.innerHTML = '⚡ Backup Now';
});
});
// Refresh backups button
document.getElementById('refresh-backups-btn')?.addEventListener('click', function() {
loadBackupList();
});
// Save schedule form
document.getElementById('backup-schedule-form')?.addEventListener('submit', function(e) {
e.preventDefault();
const formData = {
enabled: document.getElementById('schedule-enabled').checked,
time: document.getElementById('schedule-time').value,
frequency: document.getElementById('schedule-frequency').value,
retention_days: parseInt(document.getElementById('retention-days').value)
};
fetch('/api/backup/schedule', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(formData)
})
.then(response => response.json())
.then(data => {
if (data.success) {
alert('✅ ' + data.message);
} else {
alert('❌ ' + data.message);
}
})
.catch(error => {
console.error('Error saving schedule:', error);
alert('❌ Failed to save schedule');
});
});
// Download backup function
function downloadBackup(filename) {
window.location.href = `/api/backup/download/${filename}`;
}
// Delete backup function
function deleteBackup(filename) {
if (confirm(`Are you sure you want to delete backup: ${filename}?`)) {
fetch(`/api/backup/delete/${filename}`, {
method: 'DELETE'
})
.then(response => response.json())
.then(data => {
if (data.success) {
alert('✅ ' + data.message);
loadBackupList();
} else {
alert('❌ ' + data.message);
}
})
.catch(error => {
console.error('Error deleting backup:', error);
alert('❌ Failed to delete backup');
});
}
}
// Load backup data on page load
if (document.getElementById('backup-list')) {
loadBackupSchedule();
loadBackupList();
}
</script>
{% endblock %}