docs: Add comprehensive settings page analysis and improvements
- Add detailed settings page analysis report (settings.md) - Document identified security vulnerabilities and code quality issues - Provide prioritized improvement recommendations - Document permission and access control issues - Add testing checklist for validation - Track modifications to settings.py, routes.py, and settings.html templates
This commit is contained in:
252
py_app/app/templates/log_explorer.html
Normal file
252
py_app/app/templates/log_explorer.html
Normal file
@@ -0,0 +1,252 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Log Explorer{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div style="padding: 20px; max-width: 1400px; margin: 0 auto;">
|
||||
<div style="display: flex; align-items: center; gap: 15px; margin-bottom: 30px;">
|
||||
<h1 style="margin: 0; color: var(--text-primary, #333); font-size: 2em;">📋 Log Explorer</h1>
|
||||
<span style="background: var(--accent-color, #4caf50); color: white; padding: 6px 12px; border-radius: 6px; font-size: 0.85em; font-weight: 600;">Admin</span>
|
||||
</div>
|
||||
|
||||
<div style="display: grid; grid-template-columns: 350px 1fr; gap: 20px; margin-bottom: 20px;">
|
||||
<!-- Log Files List -->
|
||||
<div style="background: var(--card-bg, white); border: 1px solid var(--border-color, #ddd); border-radius: 8px; overflow: hidden; display: flex; flex-direction: column;">
|
||||
<div style="padding: 15px; background: var(--header-bg, #f5f5f5); border-bottom: 1px solid var(--border-color, #ddd); display: flex; align-items: center; gap: 8px;">
|
||||
<span style="font-size: 1.2em;">📁</span>
|
||||
<strong>Log Files</strong>
|
||||
</div>
|
||||
|
||||
<div id="logs-list" style="flex: 1; overflow-y: auto; padding: 10px; min-height: 400px;">
|
||||
<div style="text-align: center; padding: 20px; color: var(--text-secondary, #666);">
|
||||
<div style="font-size: 2em; margin-bottom: 10px;">⏳</div>
|
||||
<p>Loading log files...</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="padding: 10px; border-top: 1px solid var(--border-color, #ddd); text-align: center; font-size: 0.85em; color: var(--text-secondary, #666);">
|
||||
<span id="log-count">0</span> files
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Log Content -->
|
||||
<div style="background: var(--card-bg, white); border: 1px solid var(--border-color, #ddd); border-radius: 8px; overflow: hidden; display: flex; flex-direction: column;">
|
||||
<div style="padding: 15px; background: var(--header-bg, #f5f5f5); border-bottom: 1px solid var(--border-color, #ddd); display: flex; align-items: center; justify-content: space-between;">
|
||||
<div style="display: flex; align-items: center; gap: 8px;">
|
||||
<span style="font-size: 1.2em;">📄</span>
|
||||
<strong id="selected-log-name">Select a log file to view</strong>
|
||||
</div>
|
||||
<button id="download-log-btn" onclick="downloadCurrentLog()" style="display: none; background: #2196f3; color: white; border: none; padding: 8px 12px; border-radius: 6px; cursor: pointer; font-weight: 600; font-size: 0.9em;">
|
||||
⬇️ Download
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div id="log-content" style="flex: 1; overflow-y: auto; padding: 15px; font-family: 'Courier New', monospace; font-size: 0.85em; line-height: 1.5; background: var(--code-bg, #f9f9f9); color: var(--code-text, #333); white-space: pre-wrap; word-wrap: break-word; min-height: 400px;">
|
||||
<div style="text-align: center; padding: 40px 20px; color: var(--text-secondary, #666);">
|
||||
<div style="font-size: 2em; margin-bottom: 10px;">📖</div>
|
||||
<p>Select a log file from the list to view its contents</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Pagination -->
|
||||
<div id="pagination-controls" style="padding: 15px; background: var(--header-bg, #f5f5f5); border-top: 1px solid var(--border-color, #ddd); display: none; text-align: center; gap: 10px; display: flex; align-items: center; justify-content: center;">
|
||||
<button id="prev-page-btn" onclick="previousPage()" style="background: #2196f3; color: white; border: none; padding: 8px 12px; border-radius: 4px; cursor: pointer; font-weight: 600;">
|
||||
← Previous
|
||||
</button>
|
||||
<span id="page-info" style="font-weight: 600; color: var(--text-primary, #333);">Page 1 of 1</span>
|
||||
<button id="next-page-btn" onclick="nextPage()" style="background: #2196f3; color: white; border: none; padding: 8px 12px; border-radius: 4px; cursor: pointer; font-weight: 600;">
|
||||
Next →
|
||||
</button>
|
||||
<span id="lines-info" style="margin-left: auto; font-size: 0.9em; color: var(--text-secondary, #666);">0 total lines</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
let currentLogFile = null;
|
||||
let currentPage = 1;
|
||||
let totalPages = 1;
|
||||
|
||||
// Load log files list on page load
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
loadLogsList();
|
||||
});
|
||||
|
||||
function loadLogsList() {
|
||||
fetch('/api/logs/list')
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
renderLogsList(data.logs);
|
||||
} else {
|
||||
document.getElementById('logs-list').innerHTML = '<div style="padding: 20px; color: #d32f2f; text-align: center;">Failed to load logs</div>';
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error loading logs list:', error);
|
||||
document.getElementById('logs-list').innerHTML = '<div style="padding: 20px; color: #d32f2f; text-align: center;">Error: ' + error.message + '</div>';
|
||||
});
|
||||
}
|
||||
|
||||
function renderLogsList(logs) {
|
||||
const logsList = document.getElementById('logs-list');
|
||||
|
||||
if (logs.length === 0) {
|
||||
logsList.innerHTML = '<div style="padding: 20px; text-align: center; color: var(--text-secondary, #666);">No log files found</div>';
|
||||
document.getElementById('log-count').textContent = '0';
|
||||
return;
|
||||
}
|
||||
|
||||
let html = '';
|
||||
logs.forEach(log => {
|
||||
html += `
|
||||
<div onclick="viewLog('${log.name}')" style="padding: 12px; border-bottom: 1px solid var(--border-color, #ddd); cursor: pointer; transition: all 0.2s; background: var(--item-bg, transparent);" class="log-item" onmouseover="this.style.background='var(--hover-bg, #f0f0f0)'" onmouseout="this.style.background='var(--item-bg, transparent)'">
|
||||
<div style="display: flex; align-items: center; gap: 8px;">
|
||||
<span>📄</span>
|
||||
<div style="flex: 1; min-width: 0;">
|
||||
<div style="font-weight: 600; color: var(--text-primary, #333); word-break: break-word;">${log.name}</div>
|
||||
<div style="font-size: 0.8em; color: var(--text-secondary, #666); margin-top: 4px;">
|
||||
${log.size_formatted} • ${log.modified}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
});
|
||||
|
||||
logsList.innerHTML = html;
|
||||
document.getElementById('log-count').textContent = logs.length;
|
||||
}
|
||||
|
||||
function viewLog(filename) {
|
||||
currentLogFile = filename;
|
||||
currentPage = 1;
|
||||
loadLogContent(filename);
|
||||
}
|
||||
|
||||
function loadLogContent(filename) {
|
||||
const logContent = document.getElementById('log-content');
|
||||
logContent.innerHTML = '<div style="text-align: center; padding: 40px 20px;"><div style="font-size: 2em; margin-bottom: 10px;">⏳</div><p>Loading...</p></div>';
|
||||
|
||||
fetch(`/api/logs/view/${encodeURIComponent(filename)}?page=${currentPage}`)
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
renderLogContent(data);
|
||||
} else {
|
||||
logContent.innerHTML = `<div style="color: #d32f2f; padding: 20px;">Error: ${data.message}</div>`;
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error loading log content:', error);
|
||||
logContent.innerHTML = `<div style="color: #d32f2f; padding: 20px;">Error loading log: ${error.message}</div>`;
|
||||
});
|
||||
}
|
||||
|
||||
function renderLogContent(data) {
|
||||
const logContent = document.getElementById('log-content');
|
||||
const lines = data.lines || [];
|
||||
|
||||
if (lines.length === 0) {
|
||||
logContent.textContent = '(Empty file)';
|
||||
} else {
|
||||
logContent.textContent = lines.join('');
|
||||
}
|
||||
|
||||
// Update pagination
|
||||
totalPages = data.total_pages;
|
||||
currentPage = data.current_page;
|
||||
|
||||
const paginationControls = document.getElementById('pagination-controls');
|
||||
if (totalPages > 1) {
|
||||
paginationControls.style.display = 'flex';
|
||||
document.getElementById('page-info').textContent = `Page ${currentPage} of ${totalPages}`;
|
||||
document.getElementById('lines-info').textContent = `${data.total_lines} total lines`;
|
||||
document.getElementById('prev-page-btn').disabled = currentPage === 1;
|
||||
document.getElementById('next-page-btn').disabled = currentPage === totalPages;
|
||||
} else {
|
||||
paginationControls.style.display = 'none';
|
||||
}
|
||||
|
||||
// Update header
|
||||
document.getElementById('selected-log-name').textContent = data.filename;
|
||||
document.getElementById('download-log-btn').style.display = 'block';
|
||||
}
|
||||
|
||||
function previousPage() {
|
||||
if (currentPage > 1) {
|
||||
currentPage--;
|
||||
loadLogContent(currentLogFile);
|
||||
}
|
||||
}
|
||||
|
||||
function nextPage() {
|
||||
if (currentPage < totalPages) {
|
||||
currentPage++;
|
||||
loadLogContent(currentLogFile);
|
||||
}
|
||||
}
|
||||
|
||||
function downloadCurrentLog() {
|
||||
if (!currentLogFile) return;
|
||||
|
||||
const link = document.createElement('a');
|
||||
link.href = `/logs/${currentLogFile}`;
|
||||
link.download = currentLogFile;
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
#log-content {
|
||||
-webkit-user-select: text;
|
||||
-moz-user-select: text;
|
||||
user-select: text;
|
||||
}
|
||||
|
||||
#logs-list {
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: var(--scrollbar-color, #ccc) var(--scrollbar-bg, #f5f5f5);
|
||||
}
|
||||
|
||||
#logs-list::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
}
|
||||
|
||||
#logs-list::-webkit-scrollbar-track {
|
||||
background: var(--scrollbar-bg, #f5f5f5);
|
||||
}
|
||||
|
||||
#logs-list::-webkit-scrollbar-thumb {
|
||||
background: var(--scrollbar-color, #ccc);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
#log-content {
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: var(--scrollbar-color, #ccc) var(--scrollbar-bg, #f5f5f5);
|
||||
}
|
||||
|
||||
#log-content::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
}
|
||||
|
||||
#log-content::-webkit-scrollbar-track {
|
||||
background: var(--scrollbar-bg, #f5f5f5);
|
||||
}
|
||||
|
||||
#log-content::-webkit-scrollbar-thumb {
|
||||
background: var(--scrollbar-color, #ccc);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
div[style*="display: grid"][style*="grid-template-columns: 350px"] {
|
||||
grid-template-columns: 1fr !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
@@ -4,38 +4,6 @@
|
||||
|
||||
{% 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="db_server_domain">Server Domain/IP Address:</label>
|
||||
<input type="text" id="db_server_domain" name="server_domain" value="{{ external_settings.get('server_domain', '') }}" required>
|
||||
<label for="db_port">Port:</label>
|
||||
<input type="number" id="db_port" name="port" value="{{ external_settings.get('port', '') }}" required>
|
||||
<label for="db_database_name">Database Name:</label>
|
||||
<input type="text" id="db_database_name" name="database_name" value="{{ external_settings.get('database_name', '') }}" required>
|
||||
<label for="db_username">Username:</label>
|
||||
<input type="text" id="db_username" name="username" value="{{ external_settings.get('username', '') }}" required>
|
||||
<label for="db_password">Password:</label>
|
||||
<input type="password" id="db_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>
|
||||
@@ -101,6 +69,9 @@
|
||||
<button id="cleanup-logs-now-btn" class="btn" style="background-color: #ff9800; color: white; padding: 10px 20px; border: none; border-radius: 6px; font-weight: 600; cursor: pointer; transition: all 0.3s;">
|
||||
🗑️ Clean Up Logs Now
|
||||
</button>
|
||||
<a href="{{ url_for('main.log_explorer') }}" class="btn" style="background-color: #2196f3; color: white; padding: 10px 20px; border: none; border-radius: 6px; font-weight: 600; text-decoration: none; display: inline-block; transition: all 0.3s;">
|
||||
📖 View & Explore Logs
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div id="log-cleanup-status" style="margin-top: 15px; padding: 12px 16px; background: var(--status-bg, #e3f2fd); border-left: 4px solid var(--status-border, #2196f3); border-radius: 4px; display: none; color: var(--text-primary, #333);">
|
||||
@@ -1469,87 +1440,7 @@
|
||||
}
|
||||
</style>
|
||||
|
||||
<!-- 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="user_username">Username:</label>
|
||||
<input type="text" id="user_username" name="username" required>
|
||||
<label for="user_email">Email (Optional):</label>
|
||||
<input type="email" id="user_email" name="email">
|
||||
<label for="user_password">Password:</label>
|
||||
<input type="password" id="user_password" name="password" required>
|
||||
<label for="user_role">Role:</label>
|
||||
<select id="user_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('user_password').required = true;
|
||||
document.getElementById('user_password').placeholder = '';
|
||||
document.getElementById('user_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('user_username').value = btn.getAttribute('data-username');
|
||||
document.getElementById('user_email').value = btn.getAttribute('data-email') || '';
|
||||
document.getElementById('user_role').value = btn.getAttribute('data-role');
|
||||
document.getElementById('user_password').value = '';
|
||||
document.getElementById('user_password').required = false;
|
||||
document.getElementById('user_password').placeholder = 'Leave blank to keep current password';
|
||||
document.getElementById('user_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';
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user