Update instance_detail.html

This commit is contained in:
2025-06-10 13:51:59 +02:00
parent 0f1dc51949
commit f5e6076123

View File

@@ -93,6 +93,16 @@
<i class="fas fa-users me-2"></i>Contacts
</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link" id="logs-tab" data-bs-toggle="tab" data-bs-target="#logs" type="button" role="tab" aria-controls="logs" aria-selected="false">
<i class="fas fa-history me-2"></i>Activity Logs
</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link" id="mails-tab" data-bs-toggle="tab" data-bs-target="#mails" type="button" role="tab" aria-controls="mails" aria-selected="false">
<i class="fas fa-envelope me-2"></i>Mail Logs
</button>
</li>
</ul>
</div>
<div class="card-body">
@@ -170,6 +180,7 @@
<th>Phone</th>
<th>Company</th>
<th>Position</th>
<th>Role</th>
<th>Status</th>
<th>Actions</th>
</tr>
@@ -182,6 +193,115 @@
</table>
</div>
</div>
<!-- Logs Tab -->
<div class="tab-pane fade" id="logs" role="tabpanel" aria-labelledby="logs-tab">
<div class="row">
<div class="col-12">
<div class="card shadow-sm">
<div class="card-body">
<div class="d-flex justify-content-between align-items-center mb-4">
<h5 class="card-title mb-0">Activity Log</h5>
<div class="d-flex gap-2">
<select class="form-select form-select-sm" id="logCategory">
<option value="">All Categories</option>
<option value="AUTH">Authentication</option>
<option value="FILE">File Operations</option>
<option value="USER">User Management</option>
<option value="SYSTEM">System</option>
</select>
<select class="form-select form-select-sm" id="dateRangeFilter">
<option value="24h">Last 24 Hours</option>
<option value="7d" selected>Last 7 Days</option>
<option value="30d">Last 30 Days</option>
<option value="all">All Time</option>
</select>
<button class="btn btn-secondary btn-sm" id="clearLogFiltersBtn">
<i class="fas fa-times me-1"></i>Clear Filters
</button>
</div>
</div>
<!-- Logs Table -->
<div class="table-responsive">
<table class="table table-hover">
<thead>
<tr>
<th>Timestamp</th>
<th>Event Type</th>
<th>User</th>
<th>IP Address</th>
</tr>
</thead>
<tbody id="logsTableBody">
<!-- Logs will be loaded here via JavaScript -->
</tbody>
</table>
</div>
<!-- Pagination -->
<nav aria-label="Logs pagination" class="mt-4">
<ul class="pagination justify-content-center" id="logsPagination">
<!-- Pagination will be loaded here via JavaScript -->
</ul>
</nav>
</div>
</div>
</div>
</div>
</div>
<!-- Mails Tab -->
<div class="tab-pane fade" id="mails" role="tabpanel" aria-labelledby="mails-tab">
<div class="card shadow-sm">
<div class="card-body">
<div class="d-flex justify-content-between align-items-center mb-4">
<h5 class="card-title mb-0">Mail Log</h5>
<div class="d-flex gap-2">
<select class="form-select form-select-sm" id="statusFilter">
<option value="">All Statuses</option>
<option value="pending">Pending</option>
<option value="sent">Sent</option>
<option value="failed">Failed</option>
</select>
<select class="form-select form-select-sm" id="dateRangeFilter">
<option value="24h">Last 24 Hours</option>
<option value="7d" selected>Last 7 Days</option>
<option value="30d">Last 30 Days</option>
<option value="all">All Time</option>
</select>
<button class="btn btn-secondary btn-sm" id="clearFiltersBtn">
<i class="fas fa-times me-1"></i>Clear Filters
</button>
</div>
</div>
<!-- Mail Table -->
<div class="table-responsive">
<table class="table table-hover">
<thead>
<tr>
<th>Created At</th>
<th>Recipient</th>
<th>Subject</th>
<th>Sent At</th>
</tr>
</thead>
<tbody id="mailsTableBody">
<!-- Mails will be loaded here via JavaScript -->
</tbody>
</table>
</div>
<!-- Pagination -->
<nav aria-label="Mails pagination" class="mt-4">
<ul class="pagination justify-content-center" id="mailsPagination">
<!-- Pagination will be loaded here via JavaScript -->
</ul>
</nav>
</div>
</div>
</div>
</div>
</div>
</div>
@@ -318,6 +438,36 @@
</div>
</div>
</div>
<!-- Log Details Modal -->
<div class="modal fade" id="logDetailsModal" tabindex="-1" aria-labelledby="logDetailsModalLabel" aria-hidden="true">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="logDetailsModalLabel">Log Details</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body" id="logDetailsContent">
<!-- Log details will be loaded here -->
</div>
</div>
</div>
</div>
<!-- Mail Details Modal -->
<div class="modal fade" id="mailDetailsModal" tabindex="-1" aria-labelledby="mailDetailsModalLabel" aria-hidden="true">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="mailDetailsModalLabel">Mail Details</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body" id="mailDetailsContent">
<!-- Mail details will be loaded here -->
</div>
</div>
</div>
</div>
{% endblock %}
{% block extra_js %}
@@ -461,22 +611,17 @@ async function fetchCompanyInfo() {
// Update company name separately with debugging
const companyNameElement = document.getElementById('company-name-value');
const companyNameLabel = document.getElementById('company-name-label');
console.log('Before update - Label:', companyNameLabel?.textContent);
console.log('Before update - Value:', companyNameElement?.textContent);
// Ensure label text is set and maintained
if (companyNameLabel) {
companyNameLabel.textContent = 'Company Name:';
}
if (companyNameElement) {
companyNameElement.textContent = data.company_name || 'Not set';
}
// Ensure label text is set
if (companyNameLabel && !companyNameLabel.textContent) {
companyNameLabel.textContent = 'Company Name:';
}
console.log('After update - Label:', companyNameLabel?.textContent);
console.log('After update - Value:', companyNameElement?.textContent);
// Update company details
// Update other company details
const updateElement = (id, value) => {
const element = document.getElementById(id);
if (element && element.classList.contains('company-value')) {
@@ -594,7 +739,6 @@ window.addEventListener('beforeunload', function() {
async function fetchContacts() {
try {
// First get JWT token
console.log('Getting management token for contacts...');
const tokenResponse = await fetch(`{{ instance.main_url }}/api/admin/management-token`, {
method: 'POST',
headers: {
@@ -612,7 +756,6 @@ async function fetchContacts() {
throw new Error('No token received');
}
// Then fetch contacts using the JWT token
const response = await fetch(`{{ instance.main_url }}/api/admin/contacts`, {
headers: {
'Accept': 'application/json',
@@ -628,29 +771,44 @@ async function fetchContacts() {
const contactsList = document.getElementById('contactsTableBody');
contactsList.innerHTML = '';
if (contacts.length === 0) {
contactsList.innerHTML = `
<div class="text-center text-muted py-4">
<i class="fas fa-users fa-2x mb-3"></i>
<p>No contacts found</p>
</div>
`;
return;
}
contacts.forEach(contact => {
const row = document.createElement('tr');
// Role badge color mapping
const roleBadgeClass = {
'admin': 'bg-danger',
'manager': 'bg-warning',
'user': 'bg-info'
}[contact.role] || 'bg-secondary';
// Status badge color mapping
const statusBadgeClass = contact.is_active ? 'bg-success' : 'bg-secondary';
// Format phone number for tel: link (remove any non-digit characters)
const phoneNumber = contact.phone ? contact.phone.replace(/\D/g, '') : '';
row.innerHTML = `
<td>${contact.username}</td>
<td>${contact.email || 'Not set'}</td>
<td>${contact.phone || 'Not set'}</td>
<td>${contact.company || 'Not set'}</td>
<td>${contact.position || 'Not set'}</td>
<td>${contact.username} ${contact.last_name}</td>
<td>
<span class="badge ${contact.is_active ? 'bg-success' : 'bg-danger'}">
${contact.is_active ? 'Active' : 'Inactive'}
</span>
<a href="mailto:${contact.email}" class="text-decoration-none">
<span class="badge bg-primary">
<i class="fas fa-envelope me-1"></i>${contact.email}
</span>
</a>
</td>
<td>
${contact.phone ? `
<a href="tel:${phoneNumber}" class="text-decoration-none">
<span class="badge bg-secondary">
<i class="fas fa-phone me-1"></i>${contact.phone}
</span>
</a>
` : '-'}
</td>
<td>${contact.company || '-'}</td>
<td>${contact.position || '-'}</td>
<td><span class="badge ${roleBadgeClass}">${contact.role.charAt(0).toUpperCase() + contact.role.slice(1)}</span></td>
<td><span class="badge ${statusBadgeClass}">${contact.is_active ? 'Active' : 'Inactive'}</span></td>
<td>
<div class="btn-group">
<button class="btn btn-sm btn-outline-primary" onclick="editContact(${contact.id})">
@@ -666,13 +824,7 @@ async function fetchContacts() {
});
} catch (error) {
console.error('Error fetching contacts:', error);
const contactsList = document.getElementById('contactsTableBody');
contactsList.innerHTML = `
<div class="text-center text-danger py-4">
<i class="fas fa-exclamation-circle fa-2x mb-3"></i>
<p>Error loading contacts</p>
</div>
`;
showToast('Error loading contacts', 'error');
}
}
@@ -937,6 +1089,12 @@ document.addEventListener('DOMContentLoaded', function() {
// Fetch contacts when contacts tab is shown
document.getElementById('contacts-tab').addEventListener('shown.bs.tab', fetchContacts);
// Fetch logs when logs tab is shown
document.getElementById('logs-tab').addEventListener('shown.bs.tab', fetchLogs);
// Fetch mails when mails tab is shown
document.getElementById('mails-tab').addEventListener('shown.bs.tab', fetchMails);
// Debug the company name label
const label = document.getElementById('company-name-label');
@@ -963,5 +1121,453 @@ document.addEventListener('DOMContentLoaded', function() {
console.log('Company name label not found');
}
});
// Function to fetch logs
async function fetchLogs() {
try {
// First get JWT token
const tokenResponse = await fetch(`{{ instance.main_url }}/api/admin/management-token`, {
method: 'POST',
headers: {
'X-API-Key': '{{ instance.connection_token }}',
'Accept': 'application/json'
}
});
if (!tokenResponse.ok) {
throw new Error('Failed to get management token');
}
const tokenData = await tokenResponse.json();
if (!tokenData.token) {
throw new Error('No token received');
}
const response = await fetch(`{{ instance.main_url }}/api/admin/logs`, {
headers: {
'Accept': 'application/json',
'Authorization': `Bearer ${tokenData.token}`
}
});
if (!response.ok) {
throw new Error('Failed to fetch logs');
}
const data = await response.json();
const logsTableBody = document.getElementById('logsTableBody');
logsTableBody.innerHTML = '';
// Fetch all users to map IDs to names
const usersResponse = await fetch(`{{ instance.main_url }}/api/admin/contacts`, {
headers: {
'Accept': 'application/json',
'Authorization': `Bearer ${tokenData.token}`
}
});
if (!usersResponse.ok) {
throw new Error('Failed to fetch users');
}
const usersData = await usersResponse.json();
const userMap = new Map(usersData.map(user => [user.id, `${user.username} ${user.last_name}`]));
data.events.forEach(event => {
const row = document.createElement('tr');
row.innerHTML = `
<td>${new Date(event.timestamp).toLocaleString()}</td>
<td>${event.event_type || '-'}</td>
<td>${event.user_id ? userMap.get(event.user_id) || `User ${event.user_id}` : '-'}</td>
<td>${event.ip_address || '-'}</td>
`;
logsTableBody.appendChild(row);
});
// Update pagination
updateLogsPagination(data.current_page, data.pages);
} catch (error) {
console.error('Error fetching logs:', error);
showToast('Error loading logs', 'error');
}
}
// Function to fetch mails
async function fetchMails() {
try {
// First get JWT token
const tokenResponse = await fetch(`{{ instance.main_url }}/api/admin/management-token`, {
method: 'POST',
headers: {
'X-API-Key': '{{ instance.connection_token }}',
'Accept': 'application/json'
}
});
if (!tokenResponse.ok) {
throw new Error('Failed to get management token');
}
const tokenData = await tokenResponse.json();
if (!tokenData.token) {
throw new Error('No token received');
}
const response = await fetch(`{{ instance.main_url }}/api/admin/mail-logs`, {
headers: {
'Accept': 'application/json',
'Authorization': `Bearer ${tokenData.token}`
}
});
if (!response.ok) {
throw new Error('Failed to fetch mail logs');
}
const data = await response.json();
const mailsTableBody = document.querySelector('#mails .table tbody');
mailsTableBody.innerHTML = '';
data.mails.forEach(mail => {
const row = document.createElement('tr');
row.innerHTML = `
<td>${new Date(mail.created_at).toLocaleString()}</td>
<td>${mail.recipient}</td>
<td>${mail.subject}</td>
<td>${mail.sent_at ? new Date(mail.sent_at).toLocaleString() : '-'}</td>
`;
mailsTableBody.appendChild(row);
});
// Update pagination
updateMailsPagination(data.current_page, data.pages);
} catch (error) {
console.error('Error fetching mail logs:', error);
showToast('Error loading mail logs', 'error');
}
}
// Helper function to get mail status badge class
function getMailStatusBadgeClass(status) {
const classes = {
'pending': 'warning',
'sent': 'success',
'failed': 'danger'
};
return classes[status] || 'secondary';
}
// Function to update logs pagination
function updateLogsPagination(currentPage, totalPages) {
const pagination = document.getElementById('logsPagination');
if (!pagination) return;
pagination.innerHTML = '';
// Previous button
const prevLi = document.createElement('li');
prevLi.className = `page-item ${currentPage === 1 ? 'disabled' : ''}`;
prevLi.innerHTML = `
<button class="page-link" onclick="changeLogsPage(${currentPage - 1})" ${currentPage === 1 ? 'disabled' : ''}>
<i class="fas fa-chevron-left"></i>
</button>
`;
pagination.appendChild(prevLi);
// Page numbers
for (let i = 1; i <= totalPages; i++) {
const li = document.createElement('li');
li.className = `page-item ${i === currentPage ? 'active' : ''}`;
li.innerHTML = `
<button class="page-link" onclick="changeLogsPage(${i})">${i}</button>
`;
pagination.appendChild(li);
}
// Next button
const nextLi = document.createElement('li');
nextLi.className = `page-item ${currentPage === totalPages ? 'disabled' : ''}`;
nextLi.innerHTML = `
<button class="page-link" onclick="changeLogsPage(${currentPage + 1})" ${currentPage === totalPages ? 'disabled' : ''}>
<i class="fas fa-chevron-right"></i>
</button>
`;
pagination.appendChild(nextLi);
}
// Function to update mails pagination
function updateMailsPagination(currentPage, totalPages) {
const pagination = document.querySelector('#mails .pagination');
if (!pagination) return;
pagination.innerHTML = '';
// Previous button
const prevLi = document.createElement('li');
prevLi.className = `page-item ${currentPage === 1 ? 'disabled' : ''}`;
prevLi.innerHTML = `
<button class="page-link" onclick="changeMailsPage(${currentPage - 1})" ${currentPage === 1 ? 'disabled' : ''}>
<i class="fas fa-chevron-left"></i>
</button>
`;
pagination.appendChild(prevLi);
// Page numbers
for (let i = 1; i <= totalPages; i++) {
const li = document.createElement('li');
li.className = `page-item ${i === currentPage ? 'active' : ''}`;
li.innerHTML = `
<button class="page-link" onclick="changeMailsPage(${i})">${i}</button>
`;
pagination.appendChild(li);
}
// Next button
const nextLi = document.createElement('li');
nextLi.className = `page-item ${currentPage === totalPages ? 'disabled' : ''}`;
nextLi.innerHTML = `
<button class="page-link" onclick="changeMailsPage(${currentPage + 1})" ${currentPage === totalPages ? 'disabled' : ''}>
<i class="fas fa-chevron-right"></i>
</button>
`;
pagination.appendChild(nextLi);
}
// Function to change logs page
async function changeLogsPage(page) {
try {
// First get JWT token
const tokenResponse = await fetch(`{{ instance.main_url }}/api/admin/management-token`, {
method: 'POST',
headers: {
'X-API-Key': '{{ instance.connection_token }}',
'Accept': 'application/json'
}
});
if (!tokenResponse.ok) {
throw new Error('Failed to get management token');
}
const tokenData = await tokenResponse.json();
if (!tokenData.token) {
throw new Error('No token received');
}
const response = await fetch(`{{ instance.main_url }}/api/admin/logs?page=${page}`, {
headers: {
'Accept': 'application/json',
'Authorization': `Bearer ${tokenData.token}`
}
});
if (!response.ok) {
throw new Error('Failed to fetch logs');
}
const data = await response.json();
// Fetch all users to map IDs to names
const usersResponse = await fetch(`{{ instance.main_url }}/api/admin/contacts`, {
headers: {
'Accept': 'application/json',
'Authorization': `Bearer ${tokenData.token}`
}
});
if (!usersResponse.ok) {
throw new Error('Failed to fetch users');
}
const usersData = await usersResponse.json();
const userMap = new Map(usersData.map(user => [user.id, `${user.username} ${user.last_name}`]));
const logsTableBody = document.getElementById('logsTableBody');
logsTableBody.innerHTML = '';
data.events.forEach(event => {
const row = document.createElement('tr');
row.innerHTML = `
<td>${new Date(event.timestamp).toLocaleString()}</td>
<td>${event.event_type || '-'}</td>
<td>${event.user_id ? userMap.get(event.user_id) || `User ${event.user_id}` : '-'}</td>
<td>${event.ip_address || '-'}</td>
`;
logsTableBody.appendChild(row);
});
// Update pagination
updateLogsPagination(data.current_page, data.pages);
} catch (error) {
console.error('Error changing logs page:', error);
showToast('Error loading logs', 'error');
}
}
// Function to change mails page
async function changeMailsPage(page) {
try {
// First get JWT token
const tokenResponse = await fetch(`{{ instance.main_url }}/api/admin/management-token`, {
method: 'POST',
headers: {
'X-API-Key': '{{ instance.connection_token }}',
'Accept': 'application/json'
}
});
if (!tokenResponse.ok) {
throw new Error('Failed to get management token');
}
const tokenData = await tokenResponse.json();
if (!tokenData.token) {
throw new Error('No token received');
}
const response = await fetch(`{{ instance.main_url }}/api/admin/mail-logs?page=${page}`, {
headers: {
'Accept': 'application/json',
'Authorization': `Bearer ${tokenData.token}`
}
});
if (!response.ok) {
throw new Error('Failed to fetch mail logs');
}
const data = await response.json();
const mailsTableBody = document.querySelector('#mails .table tbody');
mailsTableBody.innerHTML = '';
data.mails.forEach(mail => {
const row = document.createElement('tr');
row.innerHTML = `
<td>${new Date(mail.created_at).toLocaleString()}</td>
<td>${mail.recipient}</td>
<td>${mail.subject}</td>
<td>${mail.sent_at ? new Date(mail.sent_at).toLocaleString() : '-'}</td>
`;
mailsTableBody.appendChild(row);
});
// Update pagination
updateMailsPagination(data.current_page, data.pages);
} catch (error) {
console.error('Error changing mail logs page:', error);
showToast('Error loading mail logs', 'error');
}
}
// Function to view mail details
async function viewMailDetails(mailId) {
try {
// First get JWT token
const tokenResponse = await fetch(`{{ instance.main_url }}/api/admin/management-token`, {
method: 'POST',
headers: {
'X-API-Key': '{{ instance.connection_token }}',
'Accept': 'application/json'
}
});
if (!tokenResponse.ok) {
throw new Error('Failed to get management token');
}
const tokenData = await tokenResponse.json();
if (!tokenData.token) {
throw new Error('No token received');
}
const response = await fetch(`{{ instance.main_url }}/api/admin/mail-logs/${mailId}`, {
headers: {
'Accept': 'application/json',
'Authorization': `Bearer ${tokenData.token}`
}
});
if (!response.ok) {
throw new Error('Failed to fetch mail details');
}
const mail = await response.json();
// Update modal content
document.getElementById('modalRecipient').textContent = mail.recipient;
document.getElementById('modalStatus').innerHTML = `
<span class="badge ${getMailStatusBadgeClass(mail.status)}">${mail.status}</span>
`;
document.getElementById('modalTemplate').textContent = mail.template_id ? mail.template_id : '-';
document.getElementById('modalCreatedAt').textContent = new Date(mail.created_at).toLocaleString();
document.getElementById('modalSentAt').textContent = mail.sent_at ? new Date(mail.sent_at).toLocaleString() : '-';
document.getElementById('modalSubject').textContent = mail.subject;
document.getElementById('modalBody').innerHTML = mail.body;
// Show modal
const modal = new bootstrap.Modal(document.getElementById('mailDetailsModal'));
modal.show();
} catch (error) {
console.error('Error viewing mail details:', error);
showToast('Error loading mail details', 'error');
}
}
// Function to view log details
function viewLogDetails(event) {
const modal = new bootstrap.Modal(document.getElementById('logDetailsModal'));
const content = document.getElementById('logDetailsContent');
// Format the details in a readable way
let details = event.details;
let formattedDetails = 'No details available';
if (details) {
if (typeof details === 'string') {
formattedDetails = details.replace(/\n/g, '<br>');
} else if (typeof details === 'object') {
formattedDetails = JSON.stringify(details, null, 2).replace(/\n/g, '<br>');
} else {
formattedDetails = String(details);
}
}
content.innerHTML = `
<div class="mb-3">
<strong>Timestamp:</strong> ${new Date(event.timestamp).toLocaleString()}
</div>
<div class="mb-3">
<strong>Event Type:</strong> ${event.event_type || '-'}
</div>
<div class="mb-3">
<strong>User:</strong> ${event.user_id ? userMap.get(event.user_id) || `User ${event.user_id}` : '-'}
</div>
<div class="mb-3">
<strong>IP Address:</strong> ${event.ip_address || '-'}
</div>
<div class="mb-3">
<strong>Details:</strong>
<div class="mt-2 p-3 bg-light rounded">
${formattedDetails}
</div>
</div>
`;
modal.show();
}
// Add event listener for clear filters button
document.addEventListener('DOMContentLoaded', function() {
// ... existing event listeners ...
const clearLogFiltersBtn = document.getElementById('clearLogFiltersBtn');
if (clearLogFiltersBtn) {
clearLogFiltersBtn.addEventListener('click', function() {
document.getElementById('logCategory').value = '';
document.getElementById('dateRangeFilter').value = '7d';
fetchLogs();
});
}
});
</script>
{% endblock %}