add connections
This commit is contained in:
274
NGINX_swagger.json
Normal file
274
NGINX_swagger.json
Normal file
@@ -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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
3
app.py
3
app.py
@@ -20,6 +20,9 @@ from utils.asset_utils import get_asset_version
|
|||||||
|
|
||||||
# Load environment variables
|
# Load environment variables
|
||||||
load_dotenv()
|
load_dotenv()
|
||||||
|
print("Environment variables after loading .env:")
|
||||||
|
print(f"MASTER: {os.getenv('MASTER')}")
|
||||||
|
print(f"ISMASTER: {os.getenv('ISMASTER')}")
|
||||||
|
|
||||||
def create_app():
|
def create_app():
|
||||||
app = Flask(__name__)
|
app = Flask(__name__)
|
||||||
|
|||||||
Binary file not shown.
@@ -11,6 +11,8 @@ import jwt
|
|||||||
from werkzeug.security import generate_password_hash
|
from werkzeug.security import generate_password_hash
|
||||||
import secrets
|
import secrets
|
||||||
from flask_login import login_user
|
from flask_login import login_user
|
||||||
|
import requests
|
||||||
|
import json
|
||||||
|
|
||||||
admin_api = Blueprint('admin_api', __name__)
|
admin_api = Blueprint('admin_api', __name__)
|
||||||
|
|
||||||
@@ -526,4 +528,334 @@ def resend_setup_mail(current_user, user_id):
|
|||||||
db.session.add(mail)
|
db.session.add(mail)
|
||||||
|
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
return jsonify({'message': 'Setup mail queued for resending'})
|
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
|
||||||
179
routes/main.py
179
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 import render_template, Blueprint, redirect, url_for, request, flash, Response, jsonify, session, current_app
|
||||||
from flask_login import current_user, login_required
|
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
|
from routes.auth import require_password_change
|
||||||
import os
|
import os
|
||||||
from werkzeug.utils import secure_filename
|
from werkzeug.utils import secure_filename
|
||||||
@@ -905,7 +905,7 @@ def init_routes(main_bp):
|
|||||||
|
|
||||||
active_tab = request.args.get('tab', 'colors')
|
active_tab = request.args.get('tab', 'colors')
|
||||||
# Validate tab parameter
|
# 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:
|
if active_tab not in valid_tabs:
|
||||||
active_tab = 'colors'
|
active_tab = 'colors'
|
||||||
|
|
||||||
@@ -917,6 +917,20 @@ def init_routes(main_bp):
|
|||||||
if active_tab == 'smtp':
|
if active_tab == 'smtp':
|
||||||
smtp_settings = KeyValueSettings.get_value('smtp_settings')
|
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
|
# Get events for the events tab
|
||||||
events = None
|
events = None
|
||||||
total_pages = 0
|
total_pages = 0
|
||||||
@@ -978,6 +992,9 @@ def init_routes(main_bp):
|
|||||||
email_templates=email_templates,
|
email_templates=email_templates,
|
||||||
form=company_form,
|
form=company_form,
|
||||||
smtp_settings=smtp_settings,
|
smtp_settings=smtp_settings,
|
||||||
|
portainer_settings=portainer_settings,
|
||||||
|
nginx_settings=nginx_settings,
|
||||||
|
git_settings=git_settings,
|
||||||
csrf_token=generate_csrf())
|
csrf_token=generate_csrf())
|
||||||
|
|
||||||
@main_bp.route('/settings/update-smtp', methods=['POST'])
|
@main_bp.route('/settings/update-smtp', methods=['POST'])
|
||||||
@@ -1493,4 +1510,160 @@ def init_routes(main_bp):
|
|||||||
headers={
|
headers={
|
||||||
'Content-Disposition': f'attachment; filename=event_log_{datetime.utcnow().strftime("%Y%m%d_%H%M%S")}.csv'
|
'Content-Disposition': f'attachment; filename=event_log_{datetime.utcnow().strftime("%Y%m%d_%H%M%S")}.csv'
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@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
|
||||||
89
templates/settings/components/connection_modals.html
Normal file
89
templates/settings/components/connection_modals.html
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
{% macro connection_modals() %}
|
||||||
|
<!-- Test Connection Modal -->
|
||||||
|
<div class="modal fade" id="testConnectionModal" tabindex="-1" aria-labelledby="testConnectionModalLabel" aria-hidden="true">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title" id="testConnectionModalLabel">Testing Connection</h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<div class="text-center">
|
||||||
|
<div class="spinner-border text-primary mb-3" role="status">
|
||||||
|
<span class="visually-hidden">Loading...</span>
|
||||||
|
</div>
|
||||||
|
<p id="testConnectionMessage">Testing connection...</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Save Connection Modal -->
|
||||||
|
<div class="modal fade" id="saveConnectionModal" tabindex="-1" aria-labelledby="saveConnectionModalLabel" aria-hidden="true">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title" id="saveConnectionModalLabel">Save Connection</h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<div class="text-center">
|
||||||
|
<div class="spinner-border text-primary mb-3" role="status">
|
||||||
|
<span class="visually-hidden">Loading...</span>
|
||||||
|
</div>
|
||||||
|
<p id="saveConnectionMessage">Saving connection settings...</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Error Modal -->
|
||||||
|
<div class="modal fade" id="errorModal" tabindex="-1" aria-labelledby="errorModalLabel" aria-hidden="true">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title" id="errorModalLabel">Error</h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<div class="text-center">
|
||||||
|
<i class="fas fa-exclamation-circle text-danger fa-3x mb-3"></i>
|
||||||
|
<p id="errorMessage"></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Success Modal -->
|
||||||
|
<div class="modal fade" id="successModal" tabindex="-1" aria-labelledby="successModalLabel" aria-hidden="true">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title" id="successModalLabel">Success</h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<div class="text-center">
|
||||||
|
<i class="fas fa-check-circle text-success fa-3x mb-3"></i>
|
||||||
|
<p id="successMessage"></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endmacro %}
|
||||||
@@ -8,6 +8,7 @@
|
|||||||
{% from "settings/tabs/email_templates.html" import email_templates_tab %}
|
{% from "settings/tabs/email_templates.html" import email_templates_tab %}
|
||||||
{% from "settings/tabs/mails.html" import mails_tab %}
|
{% from "settings/tabs/mails.html" import mails_tab %}
|
||||||
{% from "settings/tabs/smtp_settings.html" import smtp_settings_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 %}
|
{% from "settings/components/reset_colors_modal.html" import reset_colors_modal %}
|
||||||
|
|
||||||
{% block title %}Settings - DocuPulse{% endblock %}
|
{% block title %}Settings - DocuPulse{% endblock %}
|
||||||
@@ -75,6 +76,13 @@
|
|||||||
<i class="fas fa-server me-2"></i>SMTP
|
<i class="fas fa-server me-2"></i>SMTP
|
||||||
</button>
|
</button>
|
||||||
</li>
|
</li>
|
||||||
|
{% if is_master %}
|
||||||
|
<li class="nav-item" role="presentation">
|
||||||
|
<button class="nav-link {% if active_tab == 'connections' %}active{% endif %}" id="connections-tab" data-bs-toggle="tab" data-bs-target="#connections" type="button" role="tab" aria-controls="connections" aria-selected="{{ 'true' if active_tab == 'connections' else 'false' }}">
|
||||||
|
<i class="fas fa-plug me-2"></i>Connections
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
@@ -122,6 +130,13 @@
|
|||||||
<div class="tab-pane fade {% if active_tab == 'smtp' %}show active{% endif %}" id="smtp" role="tabpanel" aria-labelledby="smtp-tab">
|
<div class="tab-pane fade {% if active_tab == 'smtp' %}show active{% endif %}" id="smtp" role="tabpanel" aria-labelledby="smtp-tab">
|
||||||
{{ smtp_settings_tab(smtp_settings, csrf_token) }}
|
{{ smtp_settings_tab(smtp_settings, csrf_token) }}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{% if is_master %}
|
||||||
|
<!-- Connections Tab -->
|
||||||
|
<div class="tab-pane fade {% if active_tab == 'connections' %}show active{% endif %}" id="connections" role="tabpanel" aria-labelledby="connections-tab">
|
||||||
|
{{ connections_tab(portainer_settings, nginx_settings, site_settings, git_settings) }}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
732
templates/settings/tabs/connections.html
Normal file
732
templates/settings/tabs/connections.html
Normal file
@@ -0,0 +1,732 @@
|
|||||||
|
{% from "settings/components/connection_modals.html" import connection_modals %}
|
||||||
|
|
||||||
|
{% macro connections_tab(portainer_settings, nginx_settings, site_settings, git_settings) %}
|
||||||
|
<div class="container-fluid">
|
||||||
|
<div class="row">
|
||||||
|
<!-- Portainer Connection Card -->
|
||||||
|
<div class="col-md-6 mb-4">
|
||||||
|
<div class="card h-100">
|
||||||
|
<div class="card-header d-flex justify-content-between align-items-center">
|
||||||
|
<h5 class="mb-0">
|
||||||
|
<i class="fas fa-server me-2"></i>Portainer Connection
|
||||||
|
</h5>
|
||||||
|
<button class="btn btn-sm btn-outline-primary" onclick="testPortainerConnection()">
|
||||||
|
<i class="fas fa-plug me-1"></i>Test Connection
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<form id="portainerForm" onsubmit="savePortainerConnection(event)">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="portainerUrl" class="form-label">Portainer URL</label>
|
||||||
|
<input type="url" class="form-control" id="portainerUrl" name="portainerUrl"
|
||||||
|
placeholder="https://portainer.example.com" required
|
||||||
|
value="{{ portainer_settings.url if portainer_settings and portainer_settings.url else '' }}">
|
||||||
|
<div class="form-text">The URL of your Portainer instance</div>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="portainerApiKey" class="form-label">API Key</label>
|
||||||
|
<input type="password" class="form-control" id="portainerApiKey" name="portainerApiKey"
|
||||||
|
placeholder="Enter your Portainer API key" required
|
||||||
|
value="{{ portainer_settings.api_key if portainer_settings and portainer_settings.api_key else '' }}">
|
||||||
|
<div class="form-text">You can generate this in Portainer under Settings > API Keys</div>
|
||||||
|
</div>
|
||||||
|
<div class="d-flex justify-content-end">
|
||||||
|
<button type="submit" class="btn btn-primary">
|
||||||
|
<i class="fas fa-save me-1"></i>Save Portainer Settings
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- NGINX Connection Card -->
|
||||||
|
<div class="col-md-6 mb-4">
|
||||||
|
<div class="card h-100">
|
||||||
|
<div class="card-header d-flex justify-content-between align-items-center">
|
||||||
|
<h5 class="mb-0">
|
||||||
|
<i class="fas fa-network-wired me-2"></i>NGINX Proxy Manager Connection
|
||||||
|
</h5>
|
||||||
|
<button class="btn btn-sm btn-outline-primary" onclick="testNginxConnection()">
|
||||||
|
<i class="fas fa-plug me-1"></i>Test Connection
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<form id="nginxForm" onsubmit="saveNginxConnection(event)">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="nginxUrl" class="form-label">NGINX Proxy Manager URL</label>
|
||||||
|
<input type="url" class="form-control" id="nginxUrl" name="nginxUrl"
|
||||||
|
placeholder="https://nginx.example.com" required
|
||||||
|
value="{{ nginx_settings.url if nginx_settings and nginx_settings.url else '' }}">
|
||||||
|
<div class="form-text">The URL of your NGINX Proxy Manager instance</div>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="nginxUsername" class="form-label">Username</label>
|
||||||
|
<input type="text" class="form-control" id="nginxUsername" name="nginxUsername"
|
||||||
|
placeholder="Enter your NGINX Proxy Manager username" required
|
||||||
|
value="{{ nginx_settings.username if nginx_settings and nginx_settings.username else '' }}">
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="nginxPassword" class="form-label">Password</label>
|
||||||
|
<input type="password" class="form-control" id="nginxPassword" name="nginxPassword"
|
||||||
|
placeholder="Enter your NGINX Proxy Manager password" required
|
||||||
|
value="{{ nginx_settings.password if nginx_settings and nginx_settings.password else '' }}">
|
||||||
|
</div>
|
||||||
|
<div class="d-flex justify-content-end">
|
||||||
|
<button type="submit" class="btn btn-primary">
|
||||||
|
<i class="fas fa-save me-1"></i>Save NGINX Settings
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- GitLab Connection Card -->
|
||||||
|
<div class="col-md-6 mb-4">
|
||||||
|
<div class="card h-100">
|
||||||
|
<div class="card-header d-flex justify-content-between align-items-center">
|
||||||
|
<h5 class="mb-0">
|
||||||
|
<i class="fab fa-gitlab me-2"></i>GitLab Connection
|
||||||
|
</h5>
|
||||||
|
<button class="btn btn-sm btn-outline-primary" onclick="testGitConnection('gitlab')">
|
||||||
|
<i class="fas fa-plug me-1"></i>Test Connection
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<form id="gitlabForm" onsubmit="saveGitConnection(event, 'gitlab')">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="gitlabUrl" class="form-label">GitLab Server URL</label>
|
||||||
|
<input type="url" class="form-control" id="gitlabUrl" name="gitlabUrl"
|
||||||
|
placeholder="https://gitlab.com" required
|
||||||
|
value="{{ git_settings.url if git_settings and git_settings.provider == 'gitlab' and git_settings.url else '' }}">
|
||||||
|
<div class="form-text">The URL of your GitLab server (use https://gitlab.com for GitLab.com)</div>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="gitlabUsername" class="form-label">Username</label>
|
||||||
|
<input type="text" class="form-control" id="gitlabUsername" name="gitlabUsername"
|
||||||
|
placeholder="Enter your GitLab username" required
|
||||||
|
value="{{ git_settings.username if git_settings and git_settings.provider == 'gitlab' and git_settings.username else '' }}">
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="gitlabToken" class="form-label">Personal Access Token</label>
|
||||||
|
<input type="password" class="form-control" id="gitlabToken" name="gitlabToken"
|
||||||
|
placeholder="Enter your GitLab personal access token" required
|
||||||
|
value="{{ git_settings.token if git_settings and git_settings.provider == 'gitlab' and git_settings.token else '' }}">
|
||||||
|
<div class="form-text">You can generate this in your GitLab user settings > Access Tokens</div>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="gitlabRepo" class="form-label">Repository</label>
|
||||||
|
<div class="input-group">
|
||||||
|
<select class="form-select" id="gitlabRepo" name="gitlabRepo" required>
|
||||||
|
<option value="">Select a repository</option>
|
||||||
|
{% if git_settings and git_settings.provider == 'gitlab' and git_settings.repo %}
|
||||||
|
<option value="{{ git_settings.repo }}" selected>{{ git_settings.repo }}</option>
|
||||||
|
{% endif %}
|
||||||
|
</select>
|
||||||
|
<button class="btn btn-outline-secondary" type="button" onclick="loadGitlabRepos()">
|
||||||
|
<i class="fas fa-sync-alt"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="form-text">Select the repository to connect to</div>
|
||||||
|
</div>
|
||||||
|
<div class="d-flex justify-content-end">
|
||||||
|
<button type="submit" class="btn btn-primary">
|
||||||
|
<i class="fas fa-save me-1"></i>Save GitLab Settings
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Gitea Connection Card -->
|
||||||
|
<div class="col-md-6 mb-4">
|
||||||
|
<div class="card h-100">
|
||||||
|
<div class="card-header d-flex justify-content-between align-items-center">
|
||||||
|
<h5 class="mb-0">
|
||||||
|
<i class="fas fa-code-branch me-2"></i>Gitea Connection
|
||||||
|
</h5>
|
||||||
|
<button class="btn btn-sm btn-outline-primary" onclick="testGitConnection('gitea')">
|
||||||
|
<i class="fas fa-plug me-1"></i>Test Connection
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<form id="giteaForm" onsubmit="saveGitConnection(event, 'gitea')">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="giteaUrl" class="form-label">Gitea Server URL</label>
|
||||||
|
<input type="url" class="form-control" id="giteaUrl" name="giteaUrl"
|
||||||
|
placeholder="https://gitea.example.com" required
|
||||||
|
value="{{ git_settings.url if git_settings and git_settings.provider == 'gitea' and git_settings.url else '' }}">
|
||||||
|
<div class="form-text">The URL of your Gitea server</div>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="giteaUsername" class="form-label">Username</label>
|
||||||
|
<input type="text" class="form-control" id="giteaUsername" name="giteaUsername"
|
||||||
|
placeholder="Enter your Gitea username" required
|
||||||
|
value="{{ git_settings.username if git_settings and git_settings.provider == 'gitea' and git_settings.username else '' }}">
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="giteaPassword" class="form-label">Password</label>
|
||||||
|
<input type="password" class="form-control" id="giteaPassword" name="giteaPassword"
|
||||||
|
placeholder="Enter your Gitea password"
|
||||||
|
value="{{ git_settings.password if git_settings and git_settings.provider == 'gitea' and git_settings.password else '' }}">
|
||||||
|
<div class="form-text">Required for token generation if you don't have an existing token</div>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="giteaOtp" class="form-label">Two-Factor Code (if enabled)</label>
|
||||||
|
<input type="text" class="form-control" id="giteaOtp" name="giteaOtp"
|
||||||
|
placeholder="Enter 2FA code if enabled">
|
||||||
|
<div class="form-text">Required if you have two-factor authentication enabled</div>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="giteaToken" class="form-label">Access Token</label>
|
||||||
|
<div class="input-group">
|
||||||
|
<input type="password" class="form-control" id="giteaToken" name="giteaToken"
|
||||||
|
placeholder="Enter your Gitea access token" required
|
||||||
|
value="{{ git_settings.token if git_settings and git_settings.provider == 'gitea' and git_settings.token else '' }}">
|
||||||
|
<button class="btn btn-outline-secondary" type="button" onclick="generateGiteaToken()">
|
||||||
|
<i class="fas fa-key me-1"></i>Generate Token
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="form-text">You can generate a new token or use an existing one</div>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="giteaRepo" class="form-label">Repository</label>
|
||||||
|
<div class="input-group">
|
||||||
|
<select class="form-select" id="giteaRepo" name="giteaRepo" required>
|
||||||
|
<option value="">Select a repository</option>
|
||||||
|
{% if git_settings and git_settings.provider == 'gitea' and git_settings.repo %}
|
||||||
|
<option value="{{ git_settings.repo }}" selected>{{ git_settings.repo }}</option>
|
||||||
|
{% endif %}
|
||||||
|
</select>
|
||||||
|
<button class="btn btn-outline-secondary" type="button" onclick="loadGiteaRepos()">
|
||||||
|
<i class="fas fa-sync-alt"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="form-text">Select the repository to connect to</div>
|
||||||
|
</div>
|
||||||
|
<div class="d-flex justify-content-end">
|
||||||
|
<button type="submit" class="btn btn-primary">
|
||||||
|
<i class="fas fa-save me-1"></i>Save Gitea Settings
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Save Connection Modal -->
|
||||||
|
<div class="modal fade" id="saveConnectionModal" tabindex="-1" aria-labelledby="saveConnectionModalLabel" aria-hidden="true">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title" id="saveConnectionModalLabel">Save Connection</h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<p id="saveConnectionMessage"></p>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{ connection_modals() }}
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// Form validation
|
||||||
|
(function () {
|
||||||
|
'use strict'
|
||||||
|
var forms = document.querySelectorAll('.needs-validation')
|
||||||
|
Array.prototype.slice.call(forms).forEach(function (form) {
|
||||||
|
form.addEventListener('submit', function (event) {
|
||||||
|
if (!form.checkValidity()) {
|
||||||
|
event.preventDefault()
|
||||||
|
event.stopPropagation()
|
||||||
|
}
|
||||||
|
form.classList.add('was-validated')
|
||||||
|
}, false)
|
||||||
|
})
|
||||||
|
})()
|
||||||
|
|
||||||
|
// Show success modal
|
||||||
|
function showSuccess(message) {
|
||||||
|
const successModal = document.getElementById('successModal');
|
||||||
|
if (successModal) {
|
||||||
|
document.getElementById('successMessage').textContent = message;
|
||||||
|
new bootstrap.Modal(successModal).show();
|
||||||
|
} else {
|
||||||
|
alert(message); // Fallback if modal doesn't exist
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show error modal
|
||||||
|
function showError(message) {
|
||||||
|
const errorModal = document.getElementById('errorModal');
|
||||||
|
if (errorModal) {
|
||||||
|
document.getElementById('errorMessage').textContent = message;
|
||||||
|
new bootstrap.Modal(errorModal).show();
|
||||||
|
} else {
|
||||||
|
alert(message); // Fallback if modal doesn't exist
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get CSRF token from meta tag
|
||||||
|
function getCsrfToken() {
|
||||||
|
const metaTag = document.querySelector('meta[name="csrf-token"]');
|
||||||
|
return metaTag ? metaTag.getAttribute('content') : '';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get JWT token using management API key
|
||||||
|
async function getJwtToken() {
|
||||||
|
try {
|
||||||
|
const response = await fetch('/api/admin/management-token', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'X-API-Key': '{{ site_settings.management_api_key }}'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error('Failed to get JWT token');
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
return data.token;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error getting JWT token:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load Gitea Repositories
|
||||||
|
async function loadGiteaRepos() {
|
||||||
|
const url = document.getElementById('giteaUrl').value;
|
||||||
|
const token = document.getElementById('giteaToken').value;
|
||||||
|
const repoSelect = document.getElementById('giteaRepo');
|
||||||
|
const currentRepo = repoSelect.value; // Store current selection
|
||||||
|
|
||||||
|
if (!url || !token) {
|
||||||
|
showError('Please fill in the server URL and access token');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch('/api/admin/list-gitea-repos', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'X-CSRF-Token': getCsrfToken()
|
||||||
|
},
|
||||||
|
body: JSON.stringify({ url, token })
|
||||||
|
});
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
repoSelect.innerHTML = '<option value="">Select a repository</option>';
|
||||||
|
|
||||||
|
data.repositories.forEach(repo => {
|
||||||
|
const option = document.createElement('option');
|
||||||
|
option.value = repo.full_name;
|
||||||
|
option.textContent = repo.full_name;
|
||||||
|
if (repo.full_name === currentRepo) { // Restore selection
|
||||||
|
option.selected = true;
|
||||||
|
}
|
||||||
|
repoSelect.appendChild(option);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
throw new Error(data.message || 'Failed to load repositories');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
showError(error.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load GitLab Repositories
|
||||||
|
async function loadGitlabRepos() {
|
||||||
|
const url = document.getElementById('gitlabUrl').value;
|
||||||
|
const token = document.getElementById('gitlabToken').value;
|
||||||
|
const repoSelect = document.getElementById('gitlabRepo');
|
||||||
|
const currentRepo = repoSelect.value; // Store current selection
|
||||||
|
|
||||||
|
if (!url || !token) {
|
||||||
|
showError('Please fill in the server URL and access token');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch('/api/admin/list-gitlab-repos', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'X-CSRF-Token': getCsrfToken()
|
||||||
|
},
|
||||||
|
body: JSON.stringify({ url, token })
|
||||||
|
});
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
repoSelect.innerHTML = '<option value="">Select a repository</option>';
|
||||||
|
|
||||||
|
data.repositories.forEach(repo => {
|
||||||
|
const option = document.createElement('option');
|
||||||
|
option.value = repo.path_with_namespace;
|
||||||
|
option.textContent = repo.path_with_namespace;
|
||||||
|
if (repo.path_with_namespace === currentRepo) { // Restore selection
|
||||||
|
option.selected = true;
|
||||||
|
}
|
||||||
|
repoSelect.appendChild(option);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
throw new Error(data.message || 'Failed to load repositories');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
showError(error.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load repositories on page load if settings exist
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
const gitSettings = JSON.parse('{{ git_settings|tojson|safe }}');
|
||||||
|
if (gitSettings) {
|
||||||
|
if (gitSettings.provider === 'gitea' && gitSettings.url && gitSettings.token) {
|
||||||
|
loadGiteaRepos();
|
||||||
|
} else if (gitSettings.provider === 'gitlab' && gitSettings.url && gitSettings.token) {
|
||||||
|
loadGitlabRepos();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Test Git Connection
|
||||||
|
async function testGitConnection(provider) {
|
||||||
|
const saveModal = new bootstrap.Modal(document.getElementById('saveConnectionModal'));
|
||||||
|
const messageElement = document.getElementById('saveConnectionMessage');
|
||||||
|
messageElement.textContent = 'Testing connection...';
|
||||||
|
messageElement.className = '';
|
||||||
|
saveModal.show();
|
||||||
|
|
||||||
|
try {
|
||||||
|
let data = {};
|
||||||
|
if (provider === 'gitea') {
|
||||||
|
const url = document.getElementById('giteaUrl').value;
|
||||||
|
const username = document.getElementById('giteaUsername').value;
|
||||||
|
const token = document.getElementById('giteaToken').value;
|
||||||
|
|
||||||
|
if (!url || !username || !token) {
|
||||||
|
throw new Error('Please fill in all required fields');
|
||||||
|
}
|
||||||
|
|
||||||
|
data = {
|
||||||
|
provider: 'gitea',
|
||||||
|
url: url,
|
||||||
|
username: username,
|
||||||
|
token: token
|
||||||
|
};
|
||||||
|
} else if (provider === 'gitlab') {
|
||||||
|
const url = document.getElementById('gitlabUrl').value;
|
||||||
|
const username = document.getElementById('gitlabUsername').value;
|
||||||
|
const token = document.getElementById('gitlabToken').value;
|
||||||
|
|
||||||
|
if (!url || !username || !token) {
|
||||||
|
throw new Error('Please fill in all required fields');
|
||||||
|
}
|
||||||
|
|
||||||
|
data = {
|
||||||
|
provider: 'gitlab',
|
||||||
|
url: url,
|
||||||
|
username: username,
|
||||||
|
token: token
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await fetch('/settings/test-git-connection', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'X-CSRF-Token': getCsrfToken()
|
||||||
|
},
|
||||||
|
body: JSON.stringify(data)
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
const error = await response.json();
|
||||||
|
throw new Error(error.error || 'Connection test failed');
|
||||||
|
}
|
||||||
|
|
||||||
|
messageElement.textContent = 'Connection test successful!';
|
||||||
|
messageElement.className = 'text-success';
|
||||||
|
} catch (error) {
|
||||||
|
messageElement.textContent = error.message || 'Connection test failed';
|
||||||
|
messageElement.className = 'text-danger';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test Portainer Connection
|
||||||
|
async function testPortainerConnection() {
|
||||||
|
const saveModal = new bootstrap.Modal(document.getElementById('saveConnectionModal'));
|
||||||
|
const messageElement = document.getElementById('saveConnectionMessage');
|
||||||
|
messageElement.textContent = 'Testing connection...';
|
||||||
|
messageElement.className = '';
|
||||||
|
saveModal.show();
|
||||||
|
|
||||||
|
try {
|
||||||
|
const url = document.getElementById('portainerUrl').value;
|
||||||
|
const apiKey = document.getElementById('portainerApiKey').value;
|
||||||
|
|
||||||
|
if (!url || !apiKey) {
|
||||||
|
throw new Error('Please fill in all required fields');
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await fetch('/api/admin/test-portainer-connection', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'X-CSRF-Token': getCsrfToken()
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
url: url,
|
||||||
|
api_key: apiKey
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
const error = await response.json();
|
||||||
|
throw new Error(error.error || 'Connection test failed');
|
||||||
|
}
|
||||||
|
|
||||||
|
messageElement.textContent = 'Connection test successful!';
|
||||||
|
messageElement.className = 'text-success';
|
||||||
|
} catch (error) {
|
||||||
|
messageElement.textContent = error.message || 'Connection test failed';
|
||||||
|
messageElement.className = 'text-danger';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test NGINX Connection
|
||||||
|
async function testNginxConnection() {
|
||||||
|
const saveModal = new bootstrap.Modal(document.getElementById('saveConnectionModal'));
|
||||||
|
const messageElement = document.getElementById('saveConnectionMessage');
|
||||||
|
messageElement.textContent = 'Testing connection...';
|
||||||
|
messageElement.className = '';
|
||||||
|
saveModal.show();
|
||||||
|
|
||||||
|
try {
|
||||||
|
const url = document.getElementById('nginxUrl').value;
|
||||||
|
const username = document.getElementById('nginxUsername').value;
|
||||||
|
const password = document.getElementById('nginxPassword').value;
|
||||||
|
|
||||||
|
if (!url || !username || !password) {
|
||||||
|
throw new Error('Please fill in all required fields');
|
||||||
|
}
|
||||||
|
|
||||||
|
// First, get the token
|
||||||
|
const tokenResponse = await fetch(`${url}/api/tokens`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
identity: username,
|
||||||
|
secret: password
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!tokenResponse.ok) {
|
||||||
|
throw new Error('Failed to authenticate with NGINX Proxy Manager');
|
||||||
|
}
|
||||||
|
|
||||||
|
const tokenData = await tokenResponse.json();
|
||||||
|
const token = tokenData.token;
|
||||||
|
|
||||||
|
// Now test the connection using the token
|
||||||
|
const response = await fetch(`${url}/api/nginx/proxy-hosts`, {
|
||||||
|
method: 'GET',
|
||||||
|
headers: {
|
||||||
|
'Authorization': `Bearer ${token}`,
|
||||||
|
'Accept': 'application/json'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error('Failed to connect to NGINX Proxy Manager');
|
||||||
|
}
|
||||||
|
|
||||||
|
messageElement.textContent = 'Connection test successful!';
|
||||||
|
messageElement.className = 'text-success';
|
||||||
|
} catch (error) {
|
||||||
|
messageElement.textContent = error.message || 'Connection test failed';
|
||||||
|
messageElement.className = 'text-danger';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save Portainer Connection
|
||||||
|
async function savePortainerConnection(event) {
|
||||||
|
event.preventDefault();
|
||||||
|
const saveModal = new bootstrap.Modal(document.getElementById('saveConnectionModal'));
|
||||||
|
const messageElement = document.getElementById('saveConnectionMessage');
|
||||||
|
messageElement.textContent = '';
|
||||||
|
messageElement.className = '';
|
||||||
|
|
||||||
|
try {
|
||||||
|
const url = document.getElementById('portainerUrl').value;
|
||||||
|
const apiKey = document.getElementById('portainerApiKey').value;
|
||||||
|
|
||||||
|
if (!url || !apiKey) {
|
||||||
|
throw new Error('Please fill in all required fields');
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await fetch('/settings/save-portainer-connection', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'X-CSRF-Token': getCsrfToken()
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
url: url,
|
||||||
|
api_key: apiKey
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
const error = await response.json();
|
||||||
|
throw new Error(error.error || 'Failed to save settings');
|
||||||
|
}
|
||||||
|
|
||||||
|
messageElement.textContent = 'Settings saved successfully!';
|
||||||
|
messageElement.className = 'text-success';
|
||||||
|
} catch (error) {
|
||||||
|
messageElement.textContent = error.message || 'Failed to save settings';
|
||||||
|
messageElement.className = 'text-danger';
|
||||||
|
}
|
||||||
|
|
||||||
|
saveModal.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save NGINX Connection
|
||||||
|
async function saveNginxConnection(event) {
|
||||||
|
event.preventDefault();
|
||||||
|
const saveModal = new bootstrap.Modal(document.getElementById('saveConnectionModal'));
|
||||||
|
const messageElement = document.getElementById('saveConnectionMessage');
|
||||||
|
messageElement.textContent = '';
|
||||||
|
messageElement.className = '';
|
||||||
|
|
||||||
|
try {
|
||||||
|
const url = document.getElementById('nginxUrl').value;
|
||||||
|
const username = document.getElementById('nginxUsername').value;
|
||||||
|
const password = document.getElementById('nginxPassword').value;
|
||||||
|
|
||||||
|
if (!url || !username || !password) {
|
||||||
|
throw new Error('Please fill in all required fields');
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await fetch('/settings/save-nginx-connection', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'X-CSRF-Token': getCsrfToken()
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
url: url,
|
||||||
|
username: username,
|
||||||
|
password: password
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
const error = await response.json();
|
||||||
|
throw new Error(error.error || 'Failed to save settings');
|
||||||
|
}
|
||||||
|
|
||||||
|
messageElement.textContent = 'Settings saved successfully!';
|
||||||
|
messageElement.className = 'text-success';
|
||||||
|
} catch (error) {
|
||||||
|
messageElement.textContent = error.message || 'Failed to save settings';
|
||||||
|
messageElement.className = 'text-danger';
|
||||||
|
}
|
||||||
|
|
||||||
|
saveModal.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save Git Connection
|
||||||
|
async function saveGitConnection(event, provider) {
|
||||||
|
event.preventDefault();
|
||||||
|
const saveModal = new bootstrap.Modal(document.getElementById('saveConnectionModal'));
|
||||||
|
const messageElement = document.getElementById('saveConnectionMessage');
|
||||||
|
messageElement.textContent = '';
|
||||||
|
messageElement.className = '';
|
||||||
|
|
||||||
|
try {
|
||||||
|
let data = {};
|
||||||
|
if (provider === 'gitea') {
|
||||||
|
const url = document.getElementById('giteaUrl').value;
|
||||||
|
const username = document.getElementById('giteaUsername').value;
|
||||||
|
const token = document.getElementById('giteaToken').value;
|
||||||
|
const repo = document.getElementById('giteaRepo').value;
|
||||||
|
const password = document.getElementById('giteaPassword').value;
|
||||||
|
const otp = document.getElementById('giteaOtp').value;
|
||||||
|
|
||||||
|
if (!url || !username || !token || !repo) {
|
||||||
|
throw new Error('Please fill in all required fields');
|
||||||
|
}
|
||||||
|
|
||||||
|
data = {
|
||||||
|
provider: 'gitea',
|
||||||
|
url: url,
|
||||||
|
username: username,
|
||||||
|
token: token,
|
||||||
|
repo: repo,
|
||||||
|
password: password,
|
||||||
|
otp: otp
|
||||||
|
};
|
||||||
|
} else if (provider === 'gitlab') {
|
||||||
|
const url = document.getElementById('gitlabUrl').value;
|
||||||
|
const username = document.getElementById('gitlabUsername').value;
|
||||||
|
const token = document.getElementById('gitlabToken').value;
|
||||||
|
const repo = document.getElementById('gitlabRepo').value;
|
||||||
|
|
||||||
|
if (!url || !username || !token || !repo) {
|
||||||
|
throw new Error('Please fill in all required fields');
|
||||||
|
}
|
||||||
|
|
||||||
|
data = {
|
||||||
|
provider: 'gitlab',
|
||||||
|
url: url,
|
||||||
|
username: username,
|
||||||
|
token: token,
|
||||||
|
repo: repo
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await fetch('/settings/save-git-connection', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'X-CSRF-Token': getCsrfToken()
|
||||||
|
},
|
||||||
|
body: JSON.stringify(data)
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
const error = await response.json();
|
||||||
|
throw new Error(error.error || 'Failed to save settings');
|
||||||
|
}
|
||||||
|
|
||||||
|
messageElement.textContent = 'Settings saved successfully!';
|
||||||
|
messageElement.className = 'text-success';
|
||||||
|
} catch (error) {
|
||||||
|
messageElement.textContent = error.message || 'Failed to save settings';
|
||||||
|
messageElement.className = 'text-danger';
|
||||||
|
}
|
||||||
|
|
||||||
|
saveModal.show();
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
{% endmacro %}
|
||||||
Reference in New Issue
Block a user