diff --git a/NGINX_swagger.json b/NGINX_swagger.json new file mode 100644 index 0000000..4a502b4 --- /dev/null +++ b/NGINX_swagger.json @@ -0,0 +1,274 @@ +{ + "openapi": "3.1.0", + "info": { + "title": "Nginx Proxy Manager API", + "version": "2.x.x" + }, + "servers": [ + { + "url": "http://127.0.0.1:81/api" + } + ], + "components": { + "securitySchemes": { + "bearerAuth": { + "type": "http", + "scheme": "bearer", + "bearerFormat": "JWT" + } + } + }, + "paths": { + "/": { + "get": { + "$ref": "./paths/get.json" + } + }, + "/audit-log": { + "get": { + "$ref": "./paths/audit-log/get.json" + } + }, + "/nginx/access-lists": { + "get": { + "$ref": "./paths/nginx/access-lists/get.json" + }, + "post": { + "$ref": "./paths/nginx/access-lists/post.json" + } + }, + "/nginx/access-lists/{listID}": { + "get": { + "$ref": "./paths/nginx/access-lists/listID/get.json" + }, + "put": { + "$ref": "./paths/nginx/access-lists/listID/put.json" + }, + "delete": { + "$ref": "./paths/nginx/access-lists/listID/delete.json" + } + }, + "/nginx/certificates": { + "get": { + "$ref": "./paths/nginx/certificates/get.json" + }, + "post": { + "$ref": "./paths/nginx/certificates/post.json" + } + }, + "/nginx/certificates/validate": { + "post": { + "$ref": "./paths/nginx/certificates/validate/post.json" + } + }, + "/nginx/certificates/test-http": { + "get": { + "$ref": "./paths/nginx/certificates/test-http/get.json" + } + }, + "/nginx/certificates/{certID}": { + "get": { + "$ref": "./paths/nginx/certificates/certID/get.json" + }, + "delete": { + "$ref": "./paths/nginx/certificates/certID/delete.json" + } + }, + "/nginx/certificates/{certID}/download": { + "get": { + "$ref": "./paths/nginx/certificates/certID/download/get.json" + } + }, + "/nginx/certificates/{certID}/renew": { + "post": { + "$ref": "./paths/nginx/certificates/certID/renew/post.json" + } + }, + "/nginx/certificates/{certID}/upload": { + "post": { + "$ref": "./paths/nginx/certificates/certID/upload/post.json" + } + }, + "/nginx/proxy-hosts": { + "get": { + "$ref": "./paths/nginx/proxy-hosts/get.json" + }, + "post": { + "$ref": "./paths/nginx/proxy-hosts/post.json" + } + }, + "/nginx/proxy-hosts/{hostID}": { + "get": { + "$ref": "./paths/nginx/proxy-hosts/hostID/get.json" + }, + "put": { + "$ref": "./paths/nginx/proxy-hosts/hostID/put.json" + }, + "delete": { + "$ref": "./paths/nginx/proxy-hosts/hostID/delete.json" + } + }, + "/nginx/proxy-hosts/{hostID}/enable": { + "post": { + "$ref": "./paths/nginx/proxy-hosts/hostID/enable/post.json" + } + }, + "/nginx/proxy-hosts/{hostID}/disable": { + "post": { + "$ref": "./paths/nginx/proxy-hosts/hostID/disable/post.json" + } + }, + "/nginx/redirection-hosts": { + "get": { + "$ref": "./paths/nginx/redirection-hosts/get.json" + }, + "post": { + "$ref": "./paths/nginx/redirection-hosts/post.json" + } + }, + "/nginx/redirection-hosts/{hostID}": { + "get": { + "$ref": "./paths/nginx/redirection-hosts/hostID/get.json" + }, + "put": { + "$ref": "./paths/nginx/redirection-hosts/hostID/put.json" + }, + "delete": { + "$ref": "./paths/nginx/redirection-hosts/hostID/delete.json" + } + }, + "/nginx/redirection-hosts/{hostID}/enable": { + "post": { + "$ref": "./paths/nginx/redirection-hosts/hostID/enable/post.json" + } + }, + "/nginx/redirection-hosts/{hostID}/disable": { + "post": { + "$ref": "./paths/nginx/redirection-hosts/hostID/disable/post.json" + } + }, + "/nginx/dead-hosts": { + "get": { + "$ref": "./paths/nginx/dead-hosts/get.json" + }, + "post": { + "$ref": "./paths/nginx/dead-hosts/post.json" + } + }, + "/nginx/dead-hosts/{hostID}": { + "get": { + "$ref": "./paths/nginx/dead-hosts/hostID/get.json" + }, + "put": { + "$ref": "./paths/nginx/dead-hosts/hostID/put.json" + }, + "delete": { + "$ref": "./paths/nginx/dead-hosts/hostID/delete.json" + } + }, + "/nginx/dead-hosts/{hostID}/enable": { + "post": { + "$ref": "./paths/nginx/dead-hosts/hostID/enable/post.json" + } + }, + "/nginx/dead-hosts/{hostID}/disable": { + "post": { + "$ref": "./paths/nginx/dead-hosts/hostID/disable/post.json" + } + }, + "/nginx/streams": { + "get": { + "$ref": "./paths/nginx/streams/get.json" + }, + "post": { + "$ref": "./paths/nginx/streams/post.json" + } + }, + "/nginx/streams/{streamID}": { + "get": { + "$ref": "./paths/nginx/streams/streamID/get.json" + }, + "put": { + "$ref": "./paths/nginx/streams/streamID/put.json" + }, + "delete": { + "$ref": "./paths/nginx/streams/streamID/delete.json" + } + }, + "/nginx/streams/{streamID}/enable": { + "post": { + "$ref": "./paths/nginx/streams/streamID/enable/post.json" + } + }, + "/nginx/streams/{streamID}/disable": { + "post": { + "$ref": "./paths/nginx/streams/streamID/disable/post.json" + } + }, + "/reports/hosts": { + "get": { + "$ref": "./paths/reports/hosts/get.json" + } + }, + "/schema": { + "get": { + "$ref": "./paths/schema/get.json" + } + }, + "/settings": { + "get": { + "$ref": "./paths/settings/get.json" + } + }, + "/settings/{settingID}": { + "get": { + "$ref": "./paths/settings/settingID/get.json" + }, + "put": { + "$ref": "./paths/settings/settingID/put.json" + } + }, + "/tokens": { + "get": { + "$ref": "./paths/tokens/get.json" + }, + "post": { + "$ref": "./paths/tokens/post.json" + } + }, + "/users": { + "get": { + "$ref": "./paths/users/get.json" + }, + "post": { + "$ref": "./paths/users/post.json" + } + }, + "/users/{userID}": { + "get": { + "$ref": "./paths/users/userID/get.json" + }, + "put": { + "$ref": "./paths/users/userID/put.json" + }, + "delete": { + "$ref": "./paths/users/userID/delete.json" + } + }, + "/users/{userID}/auth": { + "put": { + "$ref": "./paths/users/userID/auth/put.json" + } + }, + "/users/{userID}/permissions": { + "put": { + "$ref": "./paths/users/userID/permissions/put.json" + } + }, + "/users/{userID}/login": { + "post": { + "$ref": "./paths/users/userID/login/post.json" + } + } + } +} diff --git a/app.py b/app.py index 7be290c..530ca12 100644 --- a/app.py +++ b/app.py @@ -20,6 +20,9 @@ from utils.asset_utils import get_asset_version # Load environment variables load_dotenv() +print("Environment variables after loading .env:") +print(f"MASTER: {os.getenv('MASTER')}") +print(f"ISMASTER: {os.getenv('ISMASTER')}") def create_app(): app = Flask(__name__) diff --git a/routes/__pycache__/main.cpython-313.pyc b/routes/__pycache__/main.cpython-313.pyc index 5a1ec7d..d1a308d 100644 Binary files a/routes/__pycache__/main.cpython-313.pyc and b/routes/__pycache__/main.cpython-313.pyc differ diff --git a/routes/admin_api.py b/routes/admin_api.py index 9671dee..f044450 100644 --- a/routes/admin_api.py +++ b/routes/admin_api.py @@ -11,6 +11,8 @@ import jwt from werkzeug.security import generate_password_hash import secrets from flask_login import login_user +import requests +import json admin_api = Blueprint('admin_api', __name__) @@ -526,4 +528,334 @@ def resend_setup_mail(current_user, user_id): db.session.add(mail) db.session.commit() - return jsonify({'message': 'Setup mail queued for resending'}) \ No newline at end of file + return jsonify({'message': 'Setup mail queued for resending'}) + +# Connection Settings +@admin_api.route('/test-portainer-connection', methods=['POST']) +@csrf.exempt +def test_portainer_connection(): + data = request.get_json() + url = data.get('url') + api_key = data.get('api_key') + + if not url or not api_key: + return jsonify({'error': 'Missing required fields'}), 400 + + try: + # Test Portainer connection + response = requests.get( + f"{url.rstrip('/')}/api/status", + headers={ + 'X-API-Key': api_key, + 'Accept': 'application/json' + }, + timeout=5 + ) + + if response.status_code == 200: + return jsonify({'message': 'Connection successful'}) + else: + return jsonify({'error': 'Failed to connect to Portainer'}), 400 + + except Exception as e: + return jsonify({'error': str(e)}), 500 + +@admin_api.route('/test-nginx-connection', methods=['POST']) +@csrf.exempt +def test_nginx_connection(): + data = request.get_json() + url = data.get('url') + username = data.get('username') + password = data.get('password') + + if not url or not username or not password: + return jsonify({'error': 'Missing required fields'}), 400 + + try: + # Test NGINX Proxy Manager connection + response = requests.get( + f"{url.rstrip('/')}/api/nginx/proxy-hosts", + auth=(username, password), + headers={'Accept': 'application/json'}, + timeout=5 + ) + + if response.status_code == 200: + return jsonify({'message': 'Connection successful'}) + else: + return jsonify({'error': 'Failed to connect to NGINX Proxy Manager'}), 400 + + except Exception as e: + return jsonify({'error': str(e)}), 500 + +@admin_api.route('/save-portainer-connection', methods=['POST']) +@csrf.exempt +@token_required +def save_portainer_connection(current_user): + if not current_user.is_admin: + return jsonify({'error': 'Unauthorized'}), 403 + + data = request.get_json() + url = data.get('url') + api_key = data.get('api_key') + + if not url or not api_key: + return jsonify({'error': 'Missing required fields'}), 400 + + try: + # Save Portainer settings + KeyValueSettings.set_value('portainer_settings', { + 'url': url, + 'api_key': api_key + }) + + return jsonify({'message': 'Settings saved successfully'}) + + except Exception as e: + return jsonify({'error': str(e)}), 500 + +@admin_api.route('/save-nginx-connection', methods=['POST']) +@csrf.exempt +@token_required +def save_nginx_connection(current_user): + if not current_user.is_admin: + return jsonify({'error': 'Unauthorized'}), 403 + + data = request.get_json() + url = data.get('url') + username = data.get('username') + password = data.get('password') + + if not url or not username or not password: + return jsonify({'error': 'Missing required fields'}), 400 + + try: + # Save NGINX Proxy Manager settings + KeyValueSettings.set_value('nginx_settings', { + 'url': url, + 'username': username, + 'password': password + }) + + return jsonify({'message': 'Settings saved successfully'}) + + except Exception as e: + return jsonify({'error': str(e)}), 500 + +@admin_api.route('/generate-gitea-token', methods=['POST']) +@csrf.exempt +def generate_gitea_token(): + """Generate a new Gitea API token""" + data = request.get_json() + if not data or 'url' not in data or 'username' not in data or 'password' not in data: + return jsonify({'message': 'Missing required fields'}), 400 + + try: + headers = { + 'Content-Type': 'application/json' + } + + if data.get('otp'): + headers['X-Gitea-OTP'] = data['otp'] + + # Generate token with required scopes + token_data = { + 'name': f'docupulse_{datetime.utcnow().strftime("%Y%m%d_%H%M%S")}', + 'scopes': [ + 'read:activitypub', + 'read:issue', + 'write:misc', + 'read:notification', + 'read:organization', + 'read:package', + 'read:repository', + 'read:user' + ] + } + + # Make request to Gitea API + response = requests.post( + f'{data["url"]}/api/v1/users/{data["username"]}/tokens', + headers=headers, + json=token_data, + auth=(data['username'], data['password']) + ) + + if response.status_code == 201: + token_data = response.json() + return jsonify({ + 'token': token_data['sha1'], + 'name': token_data['name'], + 'token_last_eight': token_data['token_last_eight'] + }), 200 + else: + return jsonify({'message': f'Failed to generate token: {response.json().get("message", "Unknown error")}'}), 400 + + except Exception as e: + return jsonify({'message': f'Failed to generate token: {str(e)}'}), 400 + +@admin_api.route('/list-gitea-repos', methods=['POST']) +@csrf.exempt +def list_gitea_repos(): + """List repositories from Gitea""" + data = request.get_json() + if not data or 'url' not in data or 'token' not in data: + return jsonify({'message': 'Missing required fields'}), 400 + + try: + # Try different authentication methods + headers = { + 'Accept': 'application/json' + } + + # First try token in Authorization header + headers['Authorization'] = f'token {data["token"]}' + + # Get user's repositories + response = requests.get( + f'{data["url"]}/api/v1/user/repos', + headers=headers + ) + + # If that fails, try token as query parameter + if response.status_code != 200: + response = requests.get( + f'{data["url"]}/api/v1/user/repos?token={data["token"]}', + headers={'Accept': 'application/json'} + ) + + if response.status_code == 200: + return jsonify({ + 'repositories': response.json() + }), 200 + else: + return jsonify({'message': f'Failed to list repositories: {response.json().get("message", "Unknown error")}'}), 400 + + except Exception as e: + return jsonify({'message': f'Failed to list repositories: {str(e)}'}), 400 + +@admin_api.route('/list-gitlab-repos', methods=['POST']) +@csrf.exempt +def list_gitlab_repos(): + """List repositories from GitLab""" + data = request.get_json() + if not data or 'url' not in data or 'token' not in data: + return jsonify({'message': 'Missing required fields'}), 400 + + try: + headers = { + 'PRIVATE-TOKEN': data['token'], + 'Accept': 'application/json' + } + + # Get user's projects (repositories) + response = requests.get( + f'{data["url"]}/api/v4/projects', + headers=headers, + params={'membership': 'true'} # Only get projects where user is a member + ) + + if response.status_code == 200: + return jsonify({ + 'repositories': response.json() + }), 200 + else: + return jsonify({'message': f'Failed to list repositories: {response.json().get("message", "Unknown error")}'}), 400 + + except Exception as e: + return jsonify({'message': f'Failed to list repositories: {str(e)}'}), 400 + +@admin_api.route('/test-git-connection', methods=['POST']) +@csrf.exempt +def test_git_connection(): + """Test the connection to a Git repository""" + data = request.get_json() + if not data or 'provider' not in data or 'url' not in data or 'username' not in data or 'token' not in data or 'repo' not in data: + return jsonify({'message': 'Missing required fields'}), 400 + + try: + if data['provider'] == 'gitea': + # Test Gitea connection with different authentication methods + headers = { + 'Accept': 'application/json' + } + + # First try token in Authorization header + headers['Authorization'] = f'token {data["token"]}' + + # Try to get repository information + response = requests.get( + f'{data["url"]}/api/v1/repos/{data["repo"]}', + headers=headers + ) + + # If that fails, try token as query parameter + if response.status_code != 200: + response = requests.get( + f'{data["url"]}/api/v1/repos/{data["repo"]}?token={data["token"]}', + headers={'Accept': 'application/json'} + ) + + if response.status_code == 200: + return jsonify({'message': 'Connection successful'}), 200 + else: + return jsonify({'message': f'Connection failed: {response.json().get("message", "Unknown error")}'}), 400 + + elif data['provider'] == 'gitlab': + # Test GitLab connection + headers = { + 'PRIVATE-TOKEN': data['token'], + 'Accept': 'application/json' + } + + # Try to get repository information + response = requests.get( + f'{data["url"]}/api/v4/projects/{data["repo"].replace("/", "%2F")}', + headers=headers + ) + + if response.status_code == 200: + return jsonify({'message': 'Connection successful'}), 200 + else: + return jsonify({'message': f'Connection failed: {response.json().get("message", "Unknown error")}'}), 400 + + else: + return jsonify({'message': 'Invalid Git provider'}), 400 + + except Exception as e: + return jsonify({'message': f'Connection failed: {str(e)}'}), 400 + +@admin_api.route('/save-git-connection', methods=['POST']) +@csrf.exempt +@token_required +def save_git_connection(current_user): + if not current_user.is_admin: + return jsonify({'error': 'Unauthorized'}), 403 + + data = request.get_json() + provider = data.get('provider') + url = data.get('url') + username = data.get('username') + token = data.get('token') + repo = data.get('repo') + + if not provider or not url or not username or not token or not repo: + return jsonify({'error': 'Missing required fields'}), 400 + + if provider not in ['gitea', 'gitlab']: + return jsonify({'error': 'Invalid provider'}), 400 + + try: + # Save Git settings + KeyValueSettings.set_value('git_settings', { + 'provider': provider, + 'url': url, + 'username': username, + 'token': token, + 'repo': repo + }) + + return jsonify({'message': 'Settings saved successfully'}) + + except Exception as e: + return jsonify({'error': str(e)}), 500 \ No newline at end of file diff --git a/routes/main.py b/routes/main.py index 3ff969a..e99f63a 100644 --- a/routes/main.py +++ b/routes/main.py @@ -1,6 +1,6 @@ from flask import render_template, Blueprint, redirect, url_for, request, flash, Response, jsonify, session, current_app from flask_login import current_user, login_required -from models import User, db, Room, RoomFile, RoomMemberPermission, SiteSettings, Event, Conversation, Message, MessageAttachment, Notif, EmailTemplate, Mail, KeyValueSettings, DocuPulseSettings, PasswordSetupToken, Instance +from models import User, db, Room, RoomFile, RoomMemberPermission, SiteSettings, Event, Conversation, Message, MessageAttachment, Notif, EmailTemplate, Mail, KeyValueSettings, DocuPulseSettings, PasswordSetupToken, Instance, ManagementAPIKey from routes.auth import require_password_change import os from werkzeug.utils import secure_filename @@ -905,7 +905,7 @@ def init_routes(main_bp): active_tab = request.args.get('tab', 'colors') # Validate tab parameter - valid_tabs = ['colors', 'general', 'email_templates', 'mails', 'security', 'events', 'debugging', 'smtp'] + valid_tabs = ['colors', 'general', 'email_templates', 'mails', 'security', 'events', 'debugging', 'smtp', 'connections'] if active_tab not in valid_tabs: active_tab = 'colors' @@ -917,6 +917,20 @@ def init_routes(main_bp): if active_tab == 'smtp': smtp_settings = KeyValueSettings.get_value('smtp_settings') + # Get connection settings for the connections tab + portainer_settings = None + nginx_settings = None + git_settings = None + if active_tab == 'connections': + portainer_settings = KeyValueSettings.get_value('portainer_settings') + nginx_settings = KeyValueSettings.get_value('nginx_settings') + git_settings = KeyValueSettings.get_value('git_settings') + + # Get management API key for the connections tab + management_api_key = ManagementAPIKey.query.filter_by(is_active=True).first() + if management_api_key: + site_settings.management_api_key = management_api_key.api_key + # Get events for the events tab events = None total_pages = 0 @@ -978,6 +992,9 @@ def init_routes(main_bp): email_templates=email_templates, form=company_form, smtp_settings=smtp_settings, + portainer_settings=portainer_settings, + nginx_settings=nginx_settings, + git_settings=git_settings, csrf_token=generate_csrf()) @main_bp.route('/settings/update-smtp', methods=['POST']) @@ -1493,4 +1510,160 @@ def init_routes(main_bp): headers={ 'Content-Disposition': f'attachment; filename=event_log_{datetime.utcnow().strftime("%Y%m%d_%H%M%S")}.csv' } - ) \ No newline at end of file + ) + + @main_bp.route('/settings/save-portainer-connection', methods=['POST']) + @login_required + def save_portainer_connection(): + if not current_user.is_admin: + return jsonify({'error': 'Unauthorized'}), 403 + + data = request.get_json() + url = data.get('url') + api_key = data.get('api_key') + + if not url or not api_key: + return jsonify({'error': 'Missing required fields'}), 400 + + try: + # Save Portainer settings + KeyValueSettings.set_value('portainer_settings', { + 'url': url, + 'api_key': api_key + }) + + return jsonify({'message': 'Settings saved successfully'}) + + except Exception as e: + return jsonify({'error': str(e)}), 500 + + @main_bp.route('/settings/save-nginx-connection', methods=['POST']) + @login_required + def save_nginx_connection(): + if not current_user.is_admin: + return jsonify({'error': 'Unauthorized'}), 403 + + data = request.get_json() + url = data.get('url') + username = data.get('username') + password = data.get('password') + + if not url or not username or not password: + return jsonify({'error': 'Missing required fields'}), 400 + + try: + # Save NGINX Proxy Manager settings + KeyValueSettings.set_value('nginx_settings', { + 'url': url, + 'username': username, + 'password': password + }) + + return jsonify({'message': 'Settings saved successfully'}) + + except Exception as e: + return jsonify({'error': str(e)}), 500 + + @main_bp.route('/settings/save-git-connection', methods=['POST']) + @login_required + def save_git_connection(): + if not current_user.is_admin: + return jsonify({'error': 'Unauthorized'}), 403 + + data = request.get_json() + provider = data.get('provider') + url = data.get('url') + username = data.get('username') + token = data.get('token') + repo = data.get('repo') + + if not provider or not url or not username or not token or not repo: + return jsonify({'error': 'Missing required fields'}), 400 + + if provider not in ['gitea', 'gitlab']: + return jsonify({'error': 'Invalid provider'}), 400 + + try: + # Save Git settings + KeyValueSettings.set_value('git_settings', { + 'provider': provider, + 'url': url, + 'username': username, + 'token': token, + 'repo': repo + }) + + return jsonify({'message': 'Settings saved successfully'}) + + except Exception as e: + return jsonify({'error': str(e)}), 500 + + @main_bp.route('/settings/test-git-connection', methods=['POST']) + @login_required + def test_git_connection(): + if not current_user.is_admin: + return jsonify({'error': 'Unauthorized'}), 403 + + data = request.get_json() + provider = data.get('provider') + url = data.get('url') + username = data.get('username') + token = data.get('token') + + if not provider or not url or not username or not token: + return jsonify({'error': 'Missing required fields'}), 400 + + if provider not in ['gitea', 'gitlab']: + return jsonify({'error': 'Invalid provider'}), 400 + + try: + if provider == 'gitea': + # Test Gitea connection with different authentication methods + headers = { + 'Accept': 'application/json' + } + + # First try token in Authorization header + headers['Authorization'] = f'token {token}' + + # Try to get user information + response = requests.get( + f'{url.rstrip("/")}/api/v1/user', + headers=headers, + timeout=5 + ) + + # If that fails, try token as query parameter + if response.status_code != 200: + response = requests.get( + f'{url.rstrip("/")}/api/v1/user?token={token}', + headers={'Accept': 'application/json'}, + timeout=5 + ) + + if response.status_code == 200: + return jsonify({'message': 'Connection successful'}) + else: + return jsonify({'error': f'Connection failed: {response.json().get("message", "Unknown error")}'}), 400 + + elif provider == 'gitlab': + # Test GitLab connection + headers = { + 'PRIVATE-TOKEN': token, + 'Accept': 'application/json' + } + + # Try to get user information + response = requests.get( + f'{url.rstrip("/")}/api/v4/user', + headers=headers, + timeout=5 + ) + + if response.status_code == 200: + return jsonify({'message': 'Connection successful'}) + else: + return jsonify({'error': f'Connection failed: {response.json().get("message", "Unknown error")}'}), 400 + + except Exception as e: + return jsonify({'error': f'Connection failed: {str(e)}'}), 400 \ No newline at end of file diff --git a/templates/settings/components/connection_modals.html b/templates/settings/components/connection_modals.html new file mode 100644 index 0000000..1d40c5b --- /dev/null +++ b/templates/settings/components/connection_modals.html @@ -0,0 +1,89 @@ +{% macro connection_modals() %} + + + + + + + + + + + +{% endmacro %} \ No newline at end of file diff --git a/templates/settings/settings.html b/templates/settings/settings.html index 754ebc7..939a914 100644 --- a/templates/settings/settings.html +++ b/templates/settings/settings.html @@ -8,6 +8,7 @@ {% from "settings/tabs/email_templates.html" import email_templates_tab %} {% from "settings/tabs/mails.html" import mails_tab %} {% from "settings/tabs/smtp_settings.html" import smtp_settings_tab %} +{% from "settings/tabs/connections.html" import connections_tab %} {% from "settings/components/reset_colors_modal.html" import reset_colors_modal %} {% block title %}Settings - DocuPulse{% endblock %} @@ -75,6 +76,13 @@ SMTP + {% if is_master %} + + {% endif %}
@@ -122,6 +130,13 @@
{{ smtp_settings_tab(smtp_settings, csrf_token) }}
+ + {% if is_master %} + +
+ {{ connections_tab(portainer_settings, nginx_settings, site_settings, git_settings) }} +
+ {% endif %}
diff --git a/templates/settings/tabs/connections.html b/templates/settings/tabs/connections.html new file mode 100644 index 0000000..179afe3 --- /dev/null +++ b/templates/settings/tabs/connections.html @@ -0,0 +1,732 @@ +{% from "settings/components/connection_modals.html" import connection_modals %} + +{% macro connections_tab(portainer_settings, nginx_settings, site_settings, git_settings) %} +
+
+ +
+
+
+
+ Portainer Connection +
+ +
+
+
+
+ + +
The URL of your Portainer instance
+
+
+ + +
You can generate this in Portainer under Settings > API Keys
+
+
+ +
+
+
+
+
+ + +
+
+
+
+ NGINX Proxy Manager Connection +
+ +
+
+
+
+ + +
The URL of your NGINX Proxy Manager instance
+
+
+ + +
+
+ + +
+
+ +
+
+
+
+
+ + +
+
+
+
+ GitLab Connection +
+ +
+
+
+
+ + +
The URL of your GitLab server (use https://gitlab.com for GitLab.com)
+
+
+ + +
+
+ + +
You can generate this in your GitLab user settings > Access Tokens
+
+
+ +
+ + +
+
Select the repository to connect to
+
+
+ +
+
+
+
+
+ + +
+
+
+
+ Gitea Connection +
+ +
+
+
+
+ + +
The URL of your Gitea server
+
+
+ + +
+
+ + +
Required for token generation if you don't have an existing token
+
+
+ + +
Required if you have two-factor authentication enabled
+
+
+ +
+ + +
+
You can generate a new token or use an existing one
+
+
+ +
+ + +
+
Select the repository to connect to
+
+
+ +
+
+
+
+
+
+ + + +
+ +{{ connection_modals() }} + + +{% endmacro %} \ No newline at end of file