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);
}
}
|