From af375a2b5cec2b22ce5410b566c323e461e4194f Mon Sep 17 00:00:00 2001 From: Kobe Date: Mon, 23 Jun 2025 15:17:17 +0200 Subject: [PATCH] version v3 --- routes/__pycache__/main.cpython-313.pyc | Bin 101807 -> 105155 bytes routes/admin_api.py | 29 +++- routes/main.py | 87 ++++++++++ templates/main/instances.html | 221 +++++++++++++++++++----- 4 files changed, 292 insertions(+), 45 deletions(-) diff --git a/routes/__pycache__/main.cpython-313.pyc b/routes/__pycache__/main.cpython-313.pyc index f9d647cee818842045b7ea6e121e00ca2ac1e56a..b9e663b4b1799cb9f99230f3342818e6cc65a6cd 100644 GIT binary patch delta 6797 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 delta 4802 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 N8)=`lt_Rr^|DV71rg8uP diff --git a/routes/admin_api.py b/routes/admin_api.py index ddf7d9d..6b0bdf6 100644 --- a/routes/admin_api.py +++ b/routes/admin_api.py @@ -563,4 +563,31 @@ def generate_password_reset_token(current_user, user_id): 'reset_url': reset_url, 'expires_at': reset_token.expires_at.isoformat(), 'user_email': user.email - }) \ No newline at end of file + }) + +# Version Information +@admin_api.route('/version-info', methods=['GET']) +@csrf.exempt +@token_required +def get_version_info(current_user): + """Get version information from environment variables""" + try: + version_info = { + 'app_version': os.environ.get('APP_VERSION', 'unknown'), + 'git_commit': os.environ.get('GIT_COMMIT', 'unknown'), + 'git_branch': os.environ.get('GIT_BRANCH', 'unknown'), + 'deployed_at': os.environ.get('DEPLOYED_AT', 'unknown'), + 'ismaster': os.environ.get('ISMASTER', 'false'), + 'port': os.environ.get('PORT', 'unknown') + } + + return jsonify(version_info) + except Exception as e: + current_app.logger.error(f"Error getting version info: {str(e)}") + return jsonify({ + 'error': str(e), + 'app_version': 'unknown', + 'git_commit': 'unknown', + 'git_branch': 'unknown', + 'deployed_at': 'unknown' + }), 500 \ No newline at end of file diff --git a/routes/main.py b/routes/main.py index 37a3e4f..f862f00 100644 --- a/routes/main.py +++ b/routes/main.py @@ -625,6 +625,93 @@ def init_routes(main_bp): 'is_valid': is_valid }) + @main_bp.route('/instances//version-info') + @login_required + @require_password_change + def instance_version_info(instance_id): + if not os.environ.get('MASTER', 'false').lower() == 'true': + return jsonify({'error': 'Unauthorized'}), 403 + + instance = Instance.query.get_or_404(instance_id) + + # Check if instance has a connection token + if not instance.connection_token: + return jsonify({ + 'error': 'Instance not authenticated', + 'deployed_version': instance.deployed_version, + 'deployed_branch': instance.deployed_branch + }) + + try: + # Get JWT token using the connection token + jwt_response = requests.post( + f"{instance.main_url.rstrip('/')}/api/admin/management-token", + headers={ + 'X-API-Key': instance.connection_token, + 'Accept': 'application/json' + }, + timeout=5 + ) + + if jwt_response.status_code != 200: + return jsonify({ + 'error': 'Failed to authenticate with instance', + 'deployed_version': instance.deployed_version, + 'deployed_branch': instance.deployed_branch + }) + + jwt_data = jwt_response.json() + jwt_token = jwt_data.get('token') + + if not jwt_token: + return jsonify({ + 'error': 'No JWT token received', + 'deployed_version': instance.deployed_version, + 'deployed_branch': instance.deployed_branch + }) + + # Fetch version information from the instance + response = requests.get( + f"{instance.main_url.rstrip('/')}/api/admin/version-info", + headers={ + 'Authorization': f'Bearer {jwt_token}', + 'Accept': 'application/json' + }, + timeout=5 + ) + + if response.status_code == 200: + version_data = response.json() + + # Update the instance with the fetched version information + instance.deployed_version = version_data.get('app_version', instance.deployed_version) + instance.deployed_branch = version_data.get('git_branch', instance.deployed_branch) + instance.version_checked_at = datetime.utcnow() + db.session.commit() + + return jsonify({ + 'success': True, + 'deployed_version': instance.deployed_version, + 'deployed_branch': instance.deployed_branch, + 'git_commit': version_data.get('git_commit'), + 'deployed_at': version_data.get('deployed_at'), + 'version_checked_at': instance.version_checked_at.isoformat() if instance.version_checked_at else None + }) + else: + return jsonify({ + 'error': f'Failed to fetch version info: {response.status_code}', + 'deployed_version': instance.deployed_version, + 'deployed_branch': instance.deployed_branch + }) + + except Exception as e: + current_app.logger.error(f"Error fetching version info: {str(e)}") + return jsonify({ + 'error': f'Error fetching version info: {str(e)}', + 'deployed_version': instance.deployed_version, + 'deployed_branch': instance.deployed_branch + }) + UPLOAD_FOLDER = '/app/uploads/profile_pics' if not os.path.exists(UPLOAD_FOLDER): os.makedirs(UPLOAD_FOLDER) diff --git a/templates/main/instances.html b/templates/main/instances.html index 76a7b47..9504a72 100644 --- a/templates/main/instances.html +++ b/templates/main/instances.html @@ -706,12 +706,20 @@ document.addEventListener('DOMContentLoaded', function() { const headerButtons = document.querySelector('.header-buttons'); if (headerButtons) { const refreshButton = document.createElement('button'); - refreshButton.className = 'btn btn-outline-primary'; - refreshButton.innerHTML = ' Refresh'; + refreshButton.className = 'btn btn-outline-primary me-2'; + refreshButton.innerHTML = ' Refresh All'; refreshButton.onclick = function() { fetchCompanyNames(); }; headerButtons.appendChild(refreshButton); + + const versionRefreshButton = document.createElement('button'); + versionRefreshButton.className = 'btn btn-outline-info'; + versionRefreshButton.innerHTML = ' Refresh Versions'; + versionRefreshButton.onclick = function() { + refreshAllVersionInfo(); + }; + headerButtons.appendChild(versionRefreshButton); } // Wait a short moment to ensure the table is rendered @@ -768,12 +776,25 @@ document.addEventListener('DOMContentLoaded', function() { updateColorPreview(); }); -// Function to check status of all instances +// Function to check all instance statuses async function checkAllInstanceStatuses() { - const statusBadges = document.querySelectorAll('[data-instance-id]'); - for (const badge of statusBadges) { + console.log('Checking all instance statuses...'); + const instances = document.querySelectorAll('[data-instance-id]'); + + for (const badge of instances) { const instanceId = badge.dataset.instanceId; await checkInstanceStatus(instanceId); + + // Also refresh version info when checking status + const instanceUrl = badge.closest('tr').querySelector('td:nth-child(7) a')?.href; + const apiKey = badge.dataset.token; + + if (instanceUrl && apiKey) { + // Fetch version info in the background (don't await to avoid blocking status checks) + fetchVersionInfo(instanceUrl, instanceId).catch(error => { + console.error(`Error fetching version info for instance ${instanceId}:`, error); + }); + } } } @@ -890,6 +911,115 @@ async function fetchInstanceStats(instanceUrl, instanceId, jwtToken) { } } +// Function to fetch version information for an instance +async function fetchVersionInfo(instanceUrl, instanceId) { + const row = document.querySelector(`[data-instance-id="${instanceId}"]`).closest('tr'); + const versionCell = row.querySelector('td:nth-child(9)'); // Version column + const branchCell = row.querySelector('td:nth-child(10)'); // Branch column + + // Show loading state + if (versionCell) { + versionCell.innerHTML = ' Loading...'; + } + if (branchCell) { + branchCell.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} for version info`); + const jwtToken = await getJWTToken(instanceUrl, apiKey); + console.log('Got JWT token for version info'); + + // Fetch version information + console.log(`Fetching version info for instance ${instanceId} from ${instanceUrl}/api/admin/version-info`); + const response = await fetch(`${instanceUrl}/api/admin/version-info`, { + 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 version data:', data); + + // Update version cell + if (versionCell) { + const appVersion = data.app_version || 'unknown'; + const gitCommit = data.git_commit || 'unknown'; + const deployedAt = data.deployed_at || 'unknown'; + + if (appVersion !== 'unknown') { + versionCell.innerHTML = ` + + ${appVersion.length > 8 ? appVersion.substring(0, 8) : appVersion} + `; + } else { + versionCell.innerHTML = 'unknown'; + } + } + + // Update branch cell + if (branchCell) { + const gitBranch = data.git_branch || 'unknown'; + + if (gitBranch !== 'unknown') { + branchCell.innerHTML = ` + + ${gitBranch} + `; + } else { + branchCell.innerHTML = 'unknown'; + } + } + + // Update tooltips + const versionBadge = versionCell?.querySelector('[data-bs-toggle="tooltip"]'); + const branchBadge = branchCell?.querySelector('[data-bs-toggle="tooltip"]'); + + if (versionBadge) { + new bootstrap.Tooltip(versionBadge); + } + if (branchBadge) { + new bootstrap.Tooltip(branchBadge); + } + + } catch (error) { + console.error(`Error fetching version info for instance ${instanceId}:`, error); + + // Show error state + if (versionCell) { + versionCell.innerHTML = ` + + Error + `; + } + if (branchCell) { + branchCell.innerHTML = ` + + Error + `; + } + + // Add tooltips for error states + const errorBadges = [versionCell, branchCell].map(cell => cell?.querySelector('[data-bs-toggle="tooltip"]')).filter(Boolean); + errorBadges.forEach(badge => new bootstrap.Tooltip(badge)); + } +} + // Function to fetch company name from instance settings async function fetchCompanyName(instanceUrl, instanceId) { const row = document.querySelector(`[data-instance-id="${instanceId}"]`).closest('tr'); @@ -977,53 +1107,31 @@ async function fetchCompanyName(instanceUrl, instanceId) { // Function to fetch company names for all instances async function fetchCompanyNames() { + console.log('Starting 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'); + for (const badge of instances) { + const instanceId = badge.dataset.instanceId; + const instanceUrl = badge.closest('tr').querySelector('td:nth-child(7) a')?.href; + const apiKey = badge.dataset.token; - // 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() - })) - }); - - // Main URL is now the 9th column (after adding Version and Branch columns) - const urlCell = row.querySelector('td:nth-child(9)'); - - 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)); + if (instanceUrl && apiKey) { + console.log(`Fetching data for instance ${instanceId}`); + loadingPromises.push( + fetchCompanyName(instanceUrl, instanceId), + fetchVersionInfo(instanceUrl, instanceId) // Add version info fetching + ); } else { + const row = badge.closest('tr'); 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 + row.querySelector('td:nth-child(5)'), // Data + row.querySelector('td:nth-child(9)'), // Version + row.querySelector('td:nth-child(10)') // Branch ]; cells.forEach(cell => { @@ -1043,7 +1151,7 @@ async function fetchCompanyNames() { try { await Promise.all(loadingPromises); - console.log('Finished fetching all company names and stats'); + console.log('Finished fetching all company names, stats, and version info'); } catch (error) { console.error('Error in fetchCompanyNames:', error); } @@ -1886,5 +1994,30 @@ function launchInstance() { // Redirect to the launch progress page window.location.href = '/instances/launch-progress'; } + +// Function to refresh all version information +async function refreshAllVersionInfo() { + console.log('Refreshing all version information...'); + const instances = document.querySelectorAll('[data-instance-id]'); + const loadingPromises = []; + + for (const badge of instances) { + const instanceId = badge.dataset.instanceId; + const instanceUrl = badge.closest('tr').querySelector('td:nth-child(7) a')?.href; + const apiKey = badge.dataset.token; + + if (instanceUrl && apiKey) { + console.log(`Refreshing version info for instance ${instanceId}`); + loadingPromises.push(fetchVersionInfo(instanceUrl, instanceId)); + } + } + + try { + await Promise.all(loadingPromises); + console.log('Finished refreshing all version information'); + } catch (error) { + console.error('Error refreshing version information:', error); + } +} {% endblock %} \ No newline at end of file