diff --git a/routes/__pycache__/main.cpython-313.pyc b/routes/__pycache__/main.cpython-313.pyc index 2163a11..9569722 100644 Binary files a/routes/__pycache__/main.cpython-313.pyc and b/routes/__pycache__/main.cpython-313.pyc differ diff --git a/routes/main.py b/routes/main.py index 89cba90..ff28ce8 100644 --- a/routes/main.py +++ b/routes/main.py @@ -366,7 +366,7 @@ def init_routes(main_bp): flash('This page is only available in master instances.', 'error') return redirect(url_for('main.dashboard')) - instances = Instance.query.all() + instances = Instance.query.order_by(Instance.name.asc()).all() # Check status for each instance for instance in instances: diff --git a/templates/main/instances.html b/templates/main/instances.html index a318fcf..294677b 100644 --- a/templates/main/instances.html +++ b/templates/main/instances.html @@ -56,11 +56,20 @@ {{ instance.conversations_count }} {{ "%.1f"|format(instance.data_size) }} GB {{ instance.payment_plan }} - {{ instance.main_url }} + + + {{ instance.main_url }} + + + {{ instance.status|title }} @@ -240,8 +249,26 @@ document.addEventListener('DOMContentLoaded', function() { return new bootstrap.Tooltip(tooltipTriggerEl); }); - // Check statuses on page load - checkAllInstanceStatuses(); + // Add refresh button to header + const headerButtons = document.querySelector('.header-buttons'); + if (headerButtons) { + const refreshButton = document.createElement('button'); + refreshButton.className = 'btn btn-outline-primary'; + refreshButton.innerHTML = ' Refresh'; + refreshButton.onclick = function() { + fetchCompanyNames(); + }; + headerButtons.appendChild(refreshButton); + } + + // Wait a short moment to ensure the table is rendered + setTimeout(() => { + // Check statuses on page load + checkAllInstanceStatuses(); + + // Fetch company names for all instances + fetchCompanyNames(); + }, 100); // Set up periodic status checks (every 30 seconds) setInterval(checkAllInstanceStatuses, 30000); @@ -295,6 +322,239 @@ async function checkInstanceStatus(instanceId) { } } +// Function to get JWT token using API key +async function getJWTToken(instanceUrl, apiKey) { + try { + const response = await fetch(`${instanceUrl}/api/admin/management-token`, { + method: 'POST', + headers: { + 'Accept': 'application/json', + 'X-API-Key': apiKey + } + }); + + if (!response.ok) { + const errorText = await response.text(); + console.error(`HTTP error ${response.status}:`, errorText); + throw new Error(`Server returned ${response.status}: ${errorText}`); + } + + const data = await response.json(); + return data.token; + } catch (error) { + console.error('Error getting JWT token:', error); + throw error; + } +} + +// Function to fetch instance statistics +async function fetchInstanceStats(instanceUrl, instanceId, jwtToken) { + try { + const response = await fetch(`${instanceUrl}/api/admin/statistics`, { + method: 'GET', + headers: { + 'Accept': 'application/json', + 'Authorization': `Bearer ${jwtToken}` + } + }); + + if (!response.ok) { + const errorText = await response.text(); + console.error(`HTTP error ${response.status}:`, errorText); + throw new Error(`Server returned ${response.status}: ${errorText}`); + } + + const data = await response.json(); + console.log('Received stats data:', data); + + const row = document.querySelector(`[data-instance-id="${instanceId}"]`).closest('tr'); + + // Update rooms count + const roomsCell = row.querySelector('td:nth-child(3)'); + if (roomsCell) { + roomsCell.textContent = data.rooms || '0'; + } + + // Update conversations count + const conversationsCell = row.querySelector('td:nth-child(4)'); + if (conversationsCell) { + conversationsCell.textContent = data.conversations || '0'; + } + + // Update data usage + const dataCell = row.querySelector('td:nth-child(5)'); + if (dataCell) { + const dataSize = data.total_storage || 0; + const dataSizeGB = (dataSize / (1024 * 1024 * 1024)).toFixed(1); + dataCell.textContent = `${dataSizeGB} GB`; + } + + return data; + } catch (error) { + console.error('Error fetching instance stats:', error); + throw error; + } +} + +// Function to fetch company name from instance settings +async function fetchCompanyName(instanceUrl, instanceId) { + const row = document.querySelector(`[data-instance-id="${instanceId}"]`).closest('tr'); + const companyCell = row.querySelector('td:nth-child(2)'); + + // Show loading state + if (companyCell) { + companyCell.innerHTML = ' Loading...'; + } + + try { + const apiKey = document.querySelector(`[data-instance-id="${instanceId}"]`).dataset.token; + if (!apiKey) { + throw new Error('No API key available'); + } + + console.log(`Getting JWT token for instance ${instanceId}`); + const jwtToken = await getJWTToken(instanceUrl, apiKey); + console.log('Got JWT token'); + + // Fetch company name + console.log(`Fetching company name for instance ${instanceId} from ${instanceUrl}/api/admin/settings`); + const response = await fetch(`${instanceUrl}/api/admin/settings`, { + method: 'GET', + headers: { + 'Accept': 'application/json', + 'Authorization': `Bearer ${jwtToken}` + } + }); + + if (!response.ok) { + const errorText = await response.text(); + console.error(`HTTP error ${response.status}:`, errorText); + throw new Error(`Server returned ${response.status}: ${errorText}`); + } + + const data = await response.json(); + console.log('Received data:', data); + + if (companyCell) { + companyCell.textContent = data.company_name || 'N/A'; + } + + // Fetch statistics using the same JWT token + await fetchInstanceStats(instanceUrl, instanceId, jwtToken); + + } catch (error) { + console.error('Error fetching company name:', error); + if (companyCell) { + const errorMessage = error.message || 'Unknown error'; + companyCell.innerHTML = ` + + Error + `; + + // Initialize tooltip + new bootstrap.Tooltip(companyCell.querySelector('[data-bs-toggle="tooltip"]')); + + // Also show error in stats cells + const row = document.querySelector(`[data-instance-id="${instanceId}"]`).closest('tr'); + const statsCells = [ + row.querySelector('td:nth-child(3)'), // Rooms + row.querySelector('td:nth-child(4)'), // Conversations + row.querySelector('td:nth-child(5)') // Data + ]; + + statsCells.forEach(cell => { + if (cell) { + cell.innerHTML = ` + + Error + `; + new bootstrap.Tooltip(cell.querySelector('[data-bs-toggle="tooltip"]')); + } + }); + } + } +} + +// Function to fetch company names for all instances +async function fetchCompanyNames() { + const instances = document.querySelectorAll('[data-instance-id]'); + const loadingPromises = []; + + console.log('Starting to fetch company names and stats for all instances'); + + for (const instance of instances) { + const instanceId = instance.dataset.instanceId; + const row = instance.closest('tr'); + + // Debug: Log all cells in the row + console.log(`Row for instance ${instanceId}:`, { + cells: Array.from(row.querySelectorAll('td')).map(td => ({ + text: td.textContent.trim(), + html: td.innerHTML.trim() + })) + }); + + // Changed from nth-child(8) to nth-child(7) since Main URL is the 7th column + const urlCell = row.querySelector('td:nth-child(7)'); + + if (!urlCell) { + console.error(`Could not find URL cell for instance ${instanceId}`); + continue; + } + + const urlLink = urlCell.querySelector('a'); + if (!urlLink) { + console.error(`Could not find URL link for instance ${instanceId}`); + continue; + } + + const instanceUrl = urlLink.getAttribute('href'); + const token = instance.dataset.token; + + console.log(`Instance ${instanceId}:`, { + url: instanceUrl, + hasToken: !!token + }); + + if (instanceUrl && token) { + loadingPromises.push(fetchCompanyName(instanceUrl, instanceId)); + } else { + const cells = [ + row.querySelector('td:nth-child(2)'), // Company + row.querySelector('td:nth-child(3)'), // Rooms + row.querySelector('td:nth-child(4)'), // Conversations + row.querySelector('td:nth-child(5)') // Data + ]; + + cells.forEach(cell => { + if (cell) { + cell.innerHTML = ` + + Not configured + `; + new bootstrap.Tooltip(cell.querySelector('[data-bs-toggle="tooltip"]')); + } + }); + } + } + + try { + await Promise.all(loadingPromises); + console.log('Finished fetching all company names and stats'); + } catch (error) { + console.error('Error in fetchCompanyNames:', error); + } +} + // Show modals function showAddInstanceModal() { document.getElementById('addInstanceForm').reset(); @@ -343,19 +603,33 @@ async function submitAddInstance() { async function editInstance(id) { try { - const response = await fetch(`/instances/${id}`); - if (!response.ok) throw new Error('Failed to fetch instance'); - - const instance = await response.json(); + // Find the row by looking for the edit button with the matching onclick handler + const editButton = document.querySelector(`button[onclick="editInstance(${id})"]`); + if (!editButton) { + throw new Error('Instance row not found'); + } + const row = editButton.closest('tr'); + if (!row) { + throw new Error('Instance row not found'); + } + + // Get the name from the first cell + const name = row.querySelector('td:first-child').textContent.trim(); + // Get the main URL from the link in the URL cell (7th column) + const urlCell = row.querySelector('td:nth-child(7)'); + const urlLink = urlCell.querySelector('a'); + const mainUrl = urlLink ? urlLink.getAttribute('href') : urlCell.textContent.trim(); + // Populate form - document.getElementById('edit_instance_id').value = instance.id; - document.getElementById('edit_name').value = instance.name; - document.getElementById('edit_main_url').value = instance.main_url; + document.getElementById('edit_instance_id').value = id; + document.getElementById('edit_name').value = name; + document.getElementById('edit_main_url').value = mainUrl; editInstanceModal.show(); } catch (error) { - alert('Error fetching instance: ' + error.message); + console.error('Error preparing instance edit:', error); + alert('Error: ' + error.message); } } @@ -379,12 +653,16 @@ async function submitEditInstance() { }) }); - if (!response.ok) throw new Error('Failed to update instance'); + if (!response.ok) { + const errorText = await response.text(); + throw new Error(`Failed to update instance: ${errorText}`); + } editInstanceModal.hide(); location.reload(); // Refresh to show updated instance } catch (error) { - alert('Error updating instance: ' + error.message); + console.error('Error updating instance:', error); + alert('Error: ' + error.message); } }