diff --git a/routes/__pycache__/main.cpython-313.pyc b/routes/__pycache__/main.cpython-313.pyc index b9e663b..63a275d 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 f862f00..9c5599d 100644 --- a/routes/main.py +++ b/routes/main.py @@ -712,6 +712,101 @@ def init_routes(main_bp): 'deployed_branch': instance.deployed_branch }) + @main_bp.route('/api/latest-version') + @login_required + @require_password_change + def get_latest_version(): + if not os.environ.get('MASTER', 'false').lower() == 'true': + return jsonify({'error': 'Unauthorized'}), 403 + + try: + # Get Git settings + git_settings = KeyValueSettings.get_value('git_settings') + if not git_settings: + return jsonify({ + 'error': 'Git settings not configured', + 'latest_version': 'unknown', + 'latest_commit': 'unknown', + 'last_checked': None + }) + + latest_tag = None + latest_commit = None + + if git_settings['provider'] == 'gitea': + headers = { + 'Accept': 'application/json', + 'Authorization': f'token {git_settings["token"]}' + } + + # Get the latest tag + tags_response = requests.get( + f'{git_settings["url"]}/api/v1/repos/{git_settings["repo"]}/tags', + headers=headers, + timeout=10 + ) + + if tags_response.status_code == 200: + tags_data = tags_response.json() + if tags_data: + # Sort tags by commit date (newest first) and get the latest + sorted_tags = sorted(tags_data, key=lambda x: x.get('commit', {}).get('created', ''), reverse=True) + if sorted_tags: + latest_tag = sorted_tags[0].get('name') + latest_commit = sorted_tags[0].get('commit', {}).get('id') + else: + # Try token as query parameter if header auth fails + tags_response = requests.get( + f'{git_settings["url"]}/api/v1/repos/{git_settings["repo"]}/tags?token={git_settings["token"]}', + headers={'Accept': 'application/json'}, + timeout=10 + ) + if tags_response.status_code == 200: + tags_data = tags_response.json() + if tags_data: + sorted_tags = sorted(tags_data, key=lambda x: x.get('commit', {}).get('created', ''), reverse=True) + if sorted_tags: + latest_tag = sorted_tags[0].get('name') + latest_commit = sorted_tags[0].get('commit', {}).get('id') + + elif git_settings['provider'] == 'gitlab': + headers = { + 'PRIVATE-TOKEN': git_settings['token'], + 'Accept': 'application/json' + } + + # Get the latest tag + tags_response = requests.get( + f'{git_settings["url"]}/api/v4/projects/{git_settings["repo"].replace("/", "%2F")}/repository/tags', + headers=headers, + params={'order_by': 'version', 'sort': 'desc', 'per_page': 1}, + timeout=10 + ) + + if tags_response.status_code == 200: + tags_data = tags_response.json() + if tags_data: + latest_tag = tags_data[0].get('name') + latest_commit = tags_data[0].get('commit', {}).get('id') + + return jsonify({ + 'success': True, + 'latest_version': latest_tag or 'unknown', + 'latest_commit': latest_commit or 'unknown', + 'repository': git_settings.get('repo', 'unknown'), + 'provider': git_settings.get('provider', 'unknown'), + 'last_checked': datetime.utcnow().isoformat() + }) + + except Exception as e: + current_app.logger.error(f"Error fetching latest version: {str(e)}") + return jsonify({ + 'error': f'Error fetching latest version: {str(e)}', + 'latest_version': 'unknown', + 'latest_commit': 'unknown', + 'last_checked': datetime.utcnow().isoformat() + }), 500 + 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 9504a72..61ec53b 100644 --- a/templates/main/instances.html +++ b/templates/main/instances.html @@ -45,27 +45,57 @@ {% block content %} {{ header( - title="Instances", - description="Manage your DocuPulse instances", + title="Instance Management", + description="Manage and monitor your DocuPulse instances", icon="fa-server", buttons=[ { 'text': 'Launch New Instance', - 'url': '#', - 'icon': 'fa-rocket', - 'class': 'btn-primary', - 'onclick': 'showAddInstanceModal()' - }, - { - 'text': 'Add Existing Instance', - 'url': '#', - 'icon': 'fa-link', - 'class': 'btn-primary', - 'onclick': 'showAddExistingInstanceModal()' + 'onclick': 'showAddInstanceModal()', + 'icon': 'fa-plus', + 'class': 'btn-primary' } ] ) }} + +