Compare commits
11 Commits
Author | SHA1 | Date | |
---|---|---|---|
8b9c100e87 | |||
6462fc6009 | |||
dbf0161133 | |||
0d56235863 | |||
5c80751484 | |||
1d2b1db9df | |||
1dd83f4230 | |||
3888e6d536 | |||
d063d7fb7f | |||
70e7782918 | |||
785fdb6dbb |
@ -1,5 +1,5 @@
|
||||
[tool.bumpversion]
|
||||
current_version = "1.4.6"
|
||||
current_version = "1.4.10"
|
||||
commit = true
|
||||
tag = true
|
||||
tag_name = "{new_version}"
|
||||
|
267
app.py
267
app.py
@ -10,6 +10,8 @@ import sys
|
||||
import redis
|
||||
import json
|
||||
import mysql.connector
|
||||
import re
|
||||
import threading
|
||||
|
||||
from lib.datetime import filter_accounts_next_30_days, filter_accounts_expired
|
||||
from lib.reqs import (get_urls, get_user_accounts, add_user_account,
|
||||
@ -107,7 +109,7 @@ def index() -> Union[Response, str]:
|
||||
@app.route('/vapid-public-key', methods=['GET'])
|
||||
def proxy_vapid_public_key():
|
||||
"""Proxies the request for the VAPID public key to the backend."""
|
||||
backend_url = f"{app.config['BASE_URL']}/vapid-public-key"
|
||||
backend_url = f"{app.config['BACKEND_URL']}/vapid-public-key"
|
||||
try:
|
||||
response = requests.get(backend_url)
|
||||
return Response(response.content, status=response.status_code, mimetype=response.headers['Content-Type'])
|
||||
@ -120,7 +122,7 @@ def proxy_save_subscription():
|
||||
if not session.get("logged_in"):
|
||||
return jsonify({'error': 'Unauthorized'}), 401
|
||||
|
||||
backend_url = f"{app.config['BASE_URL']}/save-subscription"
|
||||
backend_url = f"{app.config['BACKEND_URL']}/save-subscription"
|
||||
credentials = base64.b64decode(session["auth_credentials"]).decode()
|
||||
username, password = credentials.split(":", 1)
|
||||
|
||||
@ -140,7 +142,7 @@ def send_test_notification():
|
||||
if not session.get("logged_in"):
|
||||
return jsonify({'error': 'Unauthorized'}), 401
|
||||
|
||||
backend_url = f"{app.config['BASE_URL']}/send-test-notification"
|
||||
backend_url = f"{app.config['BACKEND_URL']}/send-test-notification"
|
||||
credentials = base64.b64decode(session["auth_credentials"]).decode()
|
||||
username, password = credentials.split(":", 1)
|
||||
|
||||
@ -159,7 +161,7 @@ def send_test_notification():
|
||||
def home() -> str:
|
||||
"""Renders the home page with account statistics."""
|
||||
if session.get("logged_in"):
|
||||
base_url = app.config["BASE_URL"]
|
||||
base_url = app.config["BACKEND_URL"]
|
||||
all_accounts = get_user_accounts(base_url, session["auth_credentials"])
|
||||
return render_template(
|
||||
"home.html",
|
||||
@ -177,7 +179,7 @@ def login() -> Union[Response, str]:
|
||||
password = request.form["password"]
|
||||
credentials = f"{username}:{password}"
|
||||
encoded_credentials = base64.b64encode(credentials.encode()).decode()
|
||||
base_url = app.config["BASE_URL"]
|
||||
base_url = app.config["BACKEND_URL"]
|
||||
login_url = f"{base_url}/Login"
|
||||
|
||||
try:
|
||||
@ -185,9 +187,11 @@ def login() -> Union[Response, str]:
|
||||
login_url, auth=requests.auth.HTTPBasicAuth(username, password)
|
||||
)
|
||||
response.raise_for_status()
|
||||
if response.json().get("auth") == "Success":
|
||||
response_data = response.json()
|
||||
if response_data.get("auth") == "Success":
|
||||
session["logged_in"] = True
|
||||
session["username"] = username
|
||||
session["username"] = response_data.get("username", username)
|
||||
session["user_id"] = response_data.get("user_id")
|
||||
session["auth_credentials"] = encoded_credentials
|
||||
next_url = request.args.get("next")
|
||||
if next_url:
|
||||
@ -205,7 +209,7 @@ def urls() -> Union[Response, str]:
|
||||
"""Renders the URLs page."""
|
||||
if not session.get("logged_in"):
|
||||
return redirect(url_for("home"))
|
||||
base_url = app.config["BASE_URL"]
|
||||
base_url = app.config["BACKEND_URL"]
|
||||
return render_template(
|
||||
"urls.html", urls=get_urls(base_url, session["auth_credentials"])
|
||||
)
|
||||
@ -216,7 +220,7 @@ def user_accounts() -> Union[Response, str]:
|
||||
"""Renders the user accounts page."""
|
||||
if not session.get("logged_in"):
|
||||
return redirect(url_for("home"))
|
||||
base_url = app.config["BASE_URL"]
|
||||
base_url = app.config["BACKEND_URL"]
|
||||
user_accounts_data = get_user_accounts(base_url, session["auth_credentials"])
|
||||
return render_template(
|
||||
"user_accounts.html",
|
||||
@ -238,7 +242,7 @@ def add_account() -> Union[Response, str]:
|
||||
"""Handles adding a new user account."""
|
||||
if not session.get("logged_in"):
|
||||
return redirect(url_for("index", next=request.url))
|
||||
base_url = app.config["BASE_URL"]
|
||||
base_url = app.config["BACKEND_URL"]
|
||||
shared_text = request.args.get('shared_text')
|
||||
|
||||
if request.method == "POST":
|
||||
@ -249,6 +253,9 @@ def add_account() -> Union[Response, str]:
|
||||
base_url, session["auth_credentials"], username, password, stream
|
||||
):
|
||||
cache.delete_memoized(user_accounts, key_prefix=make_cache_key)
|
||||
# Run the NPM config update in a background thread
|
||||
thread = threading.Thread(target=_update_npm_config_in_background)
|
||||
thread.start()
|
||||
return redirect(url_for("user_accounts"))
|
||||
|
||||
return render_template(
|
||||
@ -262,7 +269,7 @@ def delete_account() -> Response:
|
||||
"""Handles deleting a user account."""
|
||||
stream = request.form.get("stream")
|
||||
username = request.form.get("username")
|
||||
base_url = app.config["BASE_URL"]
|
||||
base_url = app.config["BACKEND_URL"]
|
||||
delete_user_account(base_url, session["auth_credentials"], stream, username)
|
||||
cache.delete_memoized(user_accounts, key_prefix=make_cache_key)
|
||||
return redirect(url_for("user_accounts"))
|
||||
@ -270,7 +277,7 @@ def delete_account() -> Response:
|
||||
@app.route("/validateAccount", methods=["POST"])
|
||||
def validate_account() -> Tuple[Response, int]:
|
||||
"""Forwards account validation requests to the backend."""
|
||||
base_url = app.config["BASE_URL"]
|
||||
base_url = app.config["BACKEND_URL"]
|
||||
validate_url = f"{base_url}/validateAccount"
|
||||
credentials = base64.b64decode(session["auth_credentials"]).decode()
|
||||
username, password = credentials.split(":", 1)
|
||||
@ -285,6 +292,9 @@ def validate_account() -> Tuple[Response, int]:
|
||||
response_data = response.json()
|
||||
if response_data.get("message") == "Account is valid and updated":
|
||||
cache.delete_memoized(user_accounts, key_prefix=make_cache_key)
|
||||
# Run the NPM config update in a background thread
|
||||
thread = threading.Thread(target=_update_npm_config_in_background)
|
||||
thread.start()
|
||||
return jsonify(response_data), response.status_code
|
||||
except requests.exceptions.RequestException as e:
|
||||
return jsonify({"error": str(e)}), 500
|
||||
@ -294,36 +304,31 @@ def stream_names() -> Union[Response, str]:
|
||||
"""Fetches and returns stream names as JSON."""
|
||||
if not session.get("logged_in"):
|
||||
return redirect(url_for("home"))
|
||||
base_url = app.config["BASE_URL"]
|
||||
base_url = app.config["BACKEND_URL"]
|
||||
return jsonify(get_stream_names(base_url, session["auth_credentials"]))
|
||||
|
||||
|
||||
@app.route('/config', methods=['GET', 'POST'])
|
||||
@app.route('/config')
|
||||
def config():
|
||||
"""Handles access to the configuration page."""
|
||||
if request.method == 'POST':
|
||||
password = request.form.get('password')
|
||||
if password == app.config['CONFIG_PASSWORD']:
|
||||
session['config_logged_in'] = True
|
||||
return redirect(url_for('config_dashboard'))
|
||||
else:
|
||||
return render_template('config.html', error='Invalid password')
|
||||
return render_template('config.html')
|
||||
if session.get('user_id') and int(session.get('user_id')) == 1:
|
||||
return redirect(url_for('config_dashboard'))
|
||||
return redirect(url_for('home'))
|
||||
|
||||
@app.route('/config/dashboard')
|
||||
def config_dashboard():
|
||||
"""Renders the configuration dashboard."""
|
||||
if not session.get('config_logged_in'):
|
||||
return redirect(url_for('config'))
|
||||
if not session.get('user_id') or int(session.get('user_id')) != 1:
|
||||
return redirect(url_for('home'))
|
||||
return render_template('config_dashboard.html')
|
||||
|
||||
@app.route('/check-expiring-accounts', methods=['POST'])
|
||||
def check_expiring_accounts():
|
||||
"""Proxies the request to check for expiring accounts to the backend."""
|
||||
if not session.get("config_logged_in"):
|
||||
if not session.get('user_id') or int(session.get('user_id')) != 1:
|
||||
return jsonify({'error': 'Unauthorized'}), 401
|
||||
|
||||
backend_url = f"{app.config['BASE_URL']}/check-expiry"
|
||||
backend_url = f"{app.config['BACKEND_URL']}/check-expiry"
|
||||
try:
|
||||
response = requests.post(backend_url)
|
||||
return Response(response.content, status=response.status_code, mimetype=response.headers['Content-Type'])
|
||||
@ -331,6 +336,216 @@ def check_expiring_accounts():
|
||||
return jsonify({"error": str(e)}), 502
|
||||
|
||||
|
||||
@app.route('/dns', methods=['GET', 'POST', 'DELETE'])
|
||||
def proxy_dns():
|
||||
"""Proxies DNS management requests to the backend."""
|
||||
if not session.get('user_id') or int(session.get('user_id')) != 1:
|
||||
return jsonify({'error': 'Unauthorized'}), 401
|
||||
|
||||
backend_url = f"{app.config['BACKEND_URL']}/dns"
|
||||
credentials = base64.b64decode(session["auth_credentials"]).decode()
|
||||
username, password = credentials.split(":", 1)
|
||||
auth = requests.auth.HTTPBasicAuth(username, password)
|
||||
|
||||
try:
|
||||
if request.method == 'GET':
|
||||
response = requests.get(backend_url, auth=auth)
|
||||
elif request.method == 'POST':
|
||||
response = requests.post(backend_url, auth=auth, json=request.get_json())
|
||||
if response.ok:
|
||||
cache.clear()
|
||||
elif request.method == 'DELETE':
|
||||
response = requests.delete(backend_url, auth=auth, json=request.get_json())
|
||||
if response.ok:
|
||||
cache.clear()
|
||||
|
||||
return Response(response.content, status=response.status_code, mimetype=response.headers['Content-Type'])
|
||||
except requests.exceptions.RequestException as e:
|
||||
return jsonify({"error": str(e)}), 502
|
||||
|
||||
|
||||
@app.route('/extra_urls', methods=['GET', 'POST', 'DELETE'])
|
||||
def proxy_extra_urls():
|
||||
"""Proxies extra URL management requests to the backend."""
|
||||
if not session.get('user_id') or int(session.get('user_id')) != 1:
|
||||
return jsonify({'error': 'Unauthorized'}), 401
|
||||
|
||||
backend_url = f"{app.config['BACKEND_URL']}/extra_urls"
|
||||
credentials = base64.b64decode(session["auth_credentials"]).decode()
|
||||
username, password = credentials.split(":", 1)
|
||||
auth = requests.auth.HTTPBasicAuth(username, password)
|
||||
|
||||
try:
|
||||
if request.method == 'GET':
|
||||
response = requests.get(backend_url, auth=auth)
|
||||
elif request.method == 'POST':
|
||||
response = requests.post(backend_url, auth=auth, json=request.get_json())
|
||||
if response.ok:
|
||||
cache.clear()
|
||||
elif request.method == 'DELETE':
|
||||
response = requests.delete(backend_url, auth=auth, json=request.get_json())
|
||||
if response.ok:
|
||||
cache.clear()
|
||||
|
||||
return Response(response.content, status=response.status_code, mimetype=response.headers['Content-Type'])
|
||||
except requests.exceptions.RequestException as e:
|
||||
return jsonify({"error": str(e)}), 502
|
||||
|
||||
|
||||
class NginxProxyManager:
|
||||
def __init__(self, host, email, password):
|
||||
self.host = host
|
||||
self.email = email
|
||||
self.password = password
|
||||
self.token = None
|
||||
|
||||
def login(self):
|
||||
url = f"{self.host}/api/tokens"
|
||||
payload = {
|
||||
"identity": self.email,
|
||||
"secret": self.password
|
||||
}
|
||||
headers = {
|
||||
"Content-Type": "application/json"
|
||||
}
|
||||
response = requests.post(url, headers=headers, data=json.dumps(payload))
|
||||
if response.status_code == 200:
|
||||
self.token = response.json()["token"]
|
||||
print("Login successful.")
|
||||
else:
|
||||
print(f"Failed to login: {response.text}")
|
||||
exit(1)
|
||||
|
||||
def get_proxy_host(self, host_id):
|
||||
if not self.token:
|
||||
self.login()
|
||||
|
||||
url = f"{self.host}/api/nginx/proxy-hosts/{host_id}"
|
||||
headers = {
|
||||
"Authorization": f"Bearer {self.token}"
|
||||
}
|
||||
response = requests.get(url, headers=headers)
|
||||
if response.status_code == 200:
|
||||
return response.json()
|
||||
else:
|
||||
print(f"Failed to get proxy host {host_id}: {response.text}")
|
||||
return None
|
||||
|
||||
def update_proxy_host_config(self, host_id, config):
|
||||
if not self.token:
|
||||
self.login()
|
||||
|
||||
url = f"{self.host}/api/nginx/proxy-hosts/{host_id}"
|
||||
|
||||
original_host_data = self.get_proxy_host(host_id)
|
||||
if not original_host_data:
|
||||
return
|
||||
|
||||
# Construct a new payload with only the allowed fields for an update
|
||||
update_payload = {
|
||||
"domain_names": original_host_data.get("domain_names", []),
|
||||
"forward_scheme": original_host_data.get("forward_scheme", "http"),
|
||||
"forward_host": original_host_data.get("forward_host"),
|
||||
"forward_port": original_host_data.get("forward_port"),
|
||||
"access_list_id": original_host_data.get("access_list_id", 0),
|
||||
"certificate_id": original_host_data.get("certificate_id", 0),
|
||||
"ssl_forced": original_host_data.get("ssl_forced", False),
|
||||
"hsts_enabled": original_host_data.get("hsts_enabled", False),
|
||||
"hsts_subdomains": original_host_data.get("hsts_subdomains", False),
|
||||
"http2_support": original_host_data.get("http2_support", False),
|
||||
"block_exploits": original_host_data.get("block_exploits", False),
|
||||
"caching_enabled": original_host_data.get("caching_enabled", False),
|
||||
"allow_websocket_upgrade": original_host_data.get("allow_websocket_upgrade", False),
|
||||
"advanced_config": config, # The updated advanced config
|
||||
"meta": original_host_data.get("meta", {}),
|
||||
"locations": original_host_data.get("locations", []),
|
||||
}
|
||||
|
||||
headers = {
|
||||
"Authorization": f"Bearer {self.token}",
|
||||
"Content-Type": "application/json"
|
||||
}
|
||||
|
||||
response = requests.put(url, headers=headers, data=json.dumps(update_payload))
|
||||
if response.status_code == 200:
|
||||
print(f"Successfully updated proxy host {host_id}")
|
||||
else:
|
||||
print(f"Failed to update proxy host {host_id}: {response.text}")
|
||||
|
||||
|
||||
|
||||
def update_config_with_streams(config, streams):
|
||||
# Get all stream names from the database
|
||||
db_stream_names = {stream['streamName'] for stream in streams}
|
||||
|
||||
# Find all location blocks in the config
|
||||
location_blocks = re.findall(r'location ~ \^/(\w+)\(\.\*\)\$ \{[^}]+\}', config)
|
||||
|
||||
# Remove location blocks that are not in the database
|
||||
for stream_name in location_blocks:
|
||||
if stream_name not in db_stream_names:
|
||||
print(f"Removing location block for stream: {stream_name}")
|
||||
pattern = re.compile(f'location ~ \\^/{re.escape(stream_name)}\\(\\.\\*\\)\\$ {{[^}}]+}}\\s*', re.DOTALL)
|
||||
config = pattern.sub('', config)
|
||||
|
||||
# Update existing stream URLs
|
||||
for stream in streams:
|
||||
stream_name = stream['streamName']
|
||||
stream_url = stream['streamURL']
|
||||
if stream_url: # Ensure there is a URL to update to
|
||||
# Use a more specific regex to avoid replacing parts of other URLs
|
||||
pattern = re.compile(f'(location ~ \\^/{re.escape(stream_name)}\\(\\.\\*\\)\\$ {{\\s*return 302 )([^;]+)(;\\s*}})')
|
||||
config = pattern.sub(f'\\1{stream_url}/$1$is_args$args\\3', config)
|
||||
|
||||
return config
|
||||
|
||||
def _update_npm_config():
|
||||
"""Helper function to update the NPM config."""
|
||||
if not session.get('user_id') or int(session.get('user_id')) != 1:
|
||||
print("Unauthorized attempt to update NPM config.")
|
||||
return
|
||||
|
||||
npm = NginxProxyManager(app.config['NPM_HOST'], app.config['NPM_EMAIL'], app.config['NPM_PASSWORD'])
|
||||
npm.login()
|
||||
host = npm.get_proxy_host(9)
|
||||
if host:
|
||||
current_config = host.get('advanced_config', '')
|
||||
|
||||
backend_url = f"{app.config['BACKEND_URL']}/get_all_stream_urls"
|
||||
credentials = base64.b64decode(session["auth_credentials"]).decode()
|
||||
username, password = credentials.split(":", 1)
|
||||
auth = requests.auth.HTTPBasicAuth(username, password)
|
||||
|
||||
try:
|
||||
response = requests.get(backend_url, auth=auth)
|
||||
response.raise_for_status()
|
||||
streams = response.json()
|
||||
except requests.exceptions.RequestException as e:
|
||||
print(f"Failed to fetch streams from backend: {e}")
|
||||
return
|
||||
|
||||
if streams:
|
||||
new_config = update_config_with_streams(current_config, streams)
|
||||
npm.update_proxy_host_config(9, new_config)
|
||||
print("NPM config updated successfully.")
|
||||
else:
|
||||
print("Failed to update NPM config.")
|
||||
|
||||
def _update_npm_config_in_background():
|
||||
with app.app_context():
|
||||
_update_npm_config()
|
||||
|
||||
@app.route('/update_host_9_config', methods=['POST'])
|
||||
def update_host_9_config():
|
||||
if not session.get('user_id') or int(session.get('user_id')) != 1:
|
||||
return jsonify({'error': 'Unauthorized'}), 401
|
||||
|
||||
thread = threading.Thread(target=_update_npm_config_in_background)
|
||||
thread.start()
|
||||
|
||||
return jsonify({'message': 'NPM config update started in the background.'}), 202
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
app.run(
|
||||
debug=app.config["DEBUG"],
|
||||
|
@ -22,6 +22,9 @@ self.addEventListener('install', function(event) {
|
||||
});
|
||||
|
||||
self.addEventListener('push', function(event) {
|
||||
console.log('[Service Worker] Push Received.');
|
||||
console.log(`[Service Worker] Push data: "${event.data.text()}"`);
|
||||
|
||||
const data = event.data.json();
|
||||
const options = {
|
||||
body: data.body,
|
||||
|
@ -46,7 +46,7 @@
|
||||
</main>
|
||||
|
||||
<footer class="bg-dark text-white text-center py-3 mt-5">
|
||||
<p>Version: <a href="{{ url_for('config') }}" style="color: inherit; text-decoration: none;">{{ version }}</a></p>
|
||||
<p>Version: {% if session.user_id and session.user_id|int == 1 %}<a href="{{ url_for('config') }}" style="color: inherit; text-decoration: none;">{{ version }}</a>{% else %}{{ version }}{% endif %}</p>
|
||||
</footer>
|
||||
|
||||
<input type="hidden" id="is-logged-in" value="{{ 'true' if session.get('logged_in') else 'false' }}">
|
||||
@ -82,6 +82,28 @@
|
||||
}, function(err) {
|
||||
console.log('ServiceWorker registration failed: ', err);
|
||||
});
|
||||
|
||||
const forceResubscribeBtn = document.getElementById('force-resubscribe-btn');
|
||||
if (forceResubscribeBtn) {
|
||||
forceResubscribeBtn.addEventListener('click', function() {
|
||||
navigator.serviceWorker.ready.then(function(registration) {
|
||||
registration.pushManager.getSubscription().then(function(subscription) {
|
||||
if (subscription) {
|
||||
subscription.unsubscribe().then(function(successful) {
|
||||
if (successful) {
|
||||
console.log('Unsubscribed successfully.');
|
||||
askPermission(registration);
|
||||
} else {
|
||||
console.log('Unsubscribe failed.');
|
||||
}
|
||||
});
|
||||
} else {
|
||||
askPermission(registration);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function askPermission(registration) {
|
||||
|
@ -1,27 +0,0 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Config Access{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h3 class="text-center">Enter Password</h3>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
{% if error %}
|
||||
<div class="alert alert-danger">{{ error }}</div>
|
||||
{% endif %}
|
||||
<form method="post" action="{{ url_for('config') }}">
|
||||
<div class="form-group">
|
||||
<label for="password">Password</label>
|
||||
<input type="password" class="form-control" id="password" name="password" required>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary btn-block">Login</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
@ -3,35 +3,80 @@
|
||||
{% block title %}Config Dashboard{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container">
|
||||
<h2>Configuration Dashboard</h2>
|
||||
<p>Welcome to the configuration page.</p>
|
||||
<button id="send-test-notification-btn" class="btn btn-primary">Send Test Notification</button>
|
||||
<button id="check-expiring-accounts-btn" class="btn btn-info">Check Expiring Accounts</button>
|
||||
<div class="container mt-4">
|
||||
<h2 class="mb-4">Configuration Dashboard</h2>
|
||||
|
||||
<div class="card mt-4">
|
||||
<div class="card-header">
|
||||
DNS Manager
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="input-group mb-3">
|
||||
<input type="text" class="form-control" id="dns-entry-input" placeholder="Enter DNS entry">
|
||||
<div class="input-group-append">
|
||||
<button class="btn btn-primary" id="add-dns-btn">Add</button>
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
Actions
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<button id="send-test-notification-btn" class="btn btn-primary">Send Test Notification</button>
|
||||
<button id="check-expiring-accounts-btn" class="btn btn-info">Check Expiring Accounts</button>
|
||||
<button id="force-resubscribe-btn" class="btn btn-warning">Force Re-subscribe</button>
|
||||
<button id="update-host-9-btn" class="btn btn-success">Update Redirect URLS</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mt-4">
|
||||
<div class="col-md-12">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
DNS Manager
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="input-group mb-3">
|
||||
<input type="text" class="form-control" id="dns-entry-input" placeholder="Enter DNS entry">
|
||||
<div class="input-group-append">
|
||||
<button class="btn btn-primary" id="add-dns-btn">Add</button>
|
||||
</div>
|
||||
</div>
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>DNS Entry</th>
|
||||
<th style="width: 10%;">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="dns-list-table-body">
|
||||
<!-- DNS entries will be loaded here -->
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mt-4">
|
||||
<div class="col-md-12">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
Extra URLs Manager
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="input-group mb-3">
|
||||
<input type="text" class="form-control" id="extra-url-input" placeholder="Enter Extra URL">
|
||||
<div class="input-group-append">
|
||||
<button class="btn btn-primary" id="add-extra-url-btn">Add</button>
|
||||
</div>
|
||||
</div>
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Extra URL</th>
|
||||
<th style="width: 10%;">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="extra-urls-table-body">
|
||||
<!-- Extra URLs will be loaded here -->
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<table class="table table-striped mt-3">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>DNS Entry</th>
|
||||
<th style="width: 10%;">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="dns-list-table-body">
|
||||
<!-- DNS entries will be loaded here -->
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@ -46,10 +91,21 @@
|
||||
const dnsEntryInput = document.getElementById('dns-entry-input');
|
||||
|
||||
function fetchDnsList() {
|
||||
fetch("{{ config.BASE_URL }}/dns")
|
||||
.then(response => response.json())
|
||||
fetch("{{ url_for('proxy_dns') }}")
|
||||
.then(response => {
|
||||
if (!response.ok) {
|
||||
// Log the error response text for debugging
|
||||
response.text().then(text => console.error('Error response from proxy:', text));
|
||||
throw new Error(`HTTP error! Status: ${response.status}`);
|
||||
}
|
||||
return response.json();
|
||||
})
|
||||
.then(data => {
|
||||
dnsListTableBody.innerHTML = '';
|
||||
if (!Array.isArray(data)) {
|
||||
console.error("Received data is not an array:", data);
|
||||
throw new Error("Invalid data format received from server.");
|
||||
}
|
||||
if (data.length === 0) {
|
||||
const row = dnsListTableBody.insertRow();
|
||||
const cell = row.insertCell();
|
||||
@ -69,13 +125,22 @@
|
||||
actionCell.appendChild(removeBtn);
|
||||
});
|
||||
}
|
||||
})
|
||||
.catch(e => {
|
||||
console.error('Error during fetchDnsList:', e);
|
||||
dnsListTableBody.innerHTML = '';
|
||||
const row = dnsListTableBody.insertRow();
|
||||
const cell = row.insertCell();
|
||||
cell.colSpan = 2;
|
||||
cell.textContent = 'Error loading DNS entries. See browser console for details.';
|
||||
cell.classList.add('text-center', 'text-danger');
|
||||
});
|
||||
}
|
||||
|
||||
function addDnsEntry() {
|
||||
const dnsEntry = dnsEntryInput.value.trim();
|
||||
if (dnsEntry) {
|
||||
fetch("{{ config.BASE_URL }}/dns", {
|
||||
fetch("{{ url_for('proxy_dns') }}", {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
@ -89,7 +154,7 @@
|
||||
}
|
||||
|
||||
function removeDnsEntry(dnsEntry) {
|
||||
fetch("{{ config.BASE_URL }}/dns", {
|
||||
fetch("{{ url_for('proxy_dns') }}", {
|
||||
method: 'DELETE',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
@ -103,6 +168,89 @@
|
||||
addDnsBtn.addEventListener('click', addDnsEntry);
|
||||
fetchDnsList();
|
||||
|
||||
// Extra URLs Manager
|
||||
const extraUrlsTableBody = document.getElementById('extra-urls-table-body');
|
||||
const addExtraUrlBtn = document.getElementById('add-extra-url-btn');
|
||||
const extraUrlInput = document.getElementById('extra-url-input');
|
||||
|
||||
function fetchExtraUrlsList() {
|
||||
fetch("{{ url_for('proxy_extra_urls') }}")
|
||||
.then(response => {
|
||||
if (!response.ok) {
|
||||
// Log the error response text for debugging
|
||||
response.text().then(text => console.error('Error response from proxy:', text));
|
||||
throw new Error(`HTTP error! Status: ${response.status}`);
|
||||
}
|
||||
return response.json();
|
||||
})
|
||||
.then(data => {
|
||||
extraUrlsTableBody.innerHTML = '';
|
||||
if (!Array.isArray(data)) {
|
||||
console.error("Received data is not an array:", data);
|
||||
throw new Error("Invalid data format received from server.");
|
||||
}
|
||||
if (data.length === 0) {
|
||||
const row = extraUrlsTableBody.insertRow();
|
||||
const cell = row.insertCell();
|
||||
cell.colSpan = 2;
|
||||
cell.textContent = 'No extra URLs found.';
|
||||
cell.classList.add('text-center');
|
||||
} else {
|
||||
data.forEach(entry => {
|
||||
const row = extraUrlsTableBody.insertRow();
|
||||
const entryCell = row.insertCell();
|
||||
entryCell.textContent = entry;
|
||||
const actionCell = row.insertCell();
|
||||
const removeBtn = document.createElement('button');
|
||||
removeBtn.className = 'btn btn-danger btn-sm';
|
||||
removeBtn.textContent = 'Delete';
|
||||
removeBtn.addEventListener('click', () => removeExtraUrl(entry));
|
||||
actionCell.appendChild(removeBtn);
|
||||
});
|
||||
}
|
||||
})
|
||||
.catch(e => {
|
||||
console.error('Error during fetchExtraUrlsList:', e);
|
||||
extraUrlsTableBody.innerHTML = '';
|
||||
const row = extraUrlsTableBody.insertRow();
|
||||
const cell = row.insertCell();
|
||||
cell.colSpan = 2;
|
||||
cell.textContent = 'Error loading extra URLs. See browser console for details.';
|
||||
cell.classList.add('text-center', 'text-danger');
|
||||
});
|
||||
}
|
||||
|
||||
function addExtraUrl() {
|
||||
const extraUrl = extraUrlInput.value.trim();
|
||||
if (extraUrl) {
|
||||
fetch("{{ url_for('proxy_extra_urls') }}", {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({ extra_url: extraUrl })
|
||||
}).then(() => {
|
||||
extraUrlInput.value = '';
|
||||
fetchExtraUrlsList();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function removeExtraUrl(extraUrl) {
|
||||
fetch("{{ url_for('proxy_extra_urls') }}", {
|
||||
method: 'DELETE',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({ extra_url: extraUrl })
|
||||
}).then(() => {
|
||||
fetchExtraUrlsList();
|
||||
});
|
||||
}
|
||||
|
||||
addExtraUrlBtn.addEventListener('click', addExtraUrl);
|
||||
fetchExtraUrlsList();
|
||||
|
||||
// Other buttons
|
||||
document.getElementById('send-test-notification-btn').addEventListener('click', function() {
|
||||
fetch('{{ url_for("send_test_notification") }}', {
|
||||
@ -133,6 +281,21 @@
|
||||
alert('An error occurred while triggering the expiring accounts check.');
|
||||
});
|
||||
});
|
||||
|
||||
document.getElementById('update-host-9-btn').addEventListener('click', function() {
|
||||
fetch('{{ url_for("update_host_9_config") }}', {
|
||||
method: 'POST'
|
||||
}).then(response => {
|
||||
if (response.ok) {
|
||||
alert('Host 9 config updated successfully!');
|
||||
} else {
|
||||
alert('Failed to update Host 9 config.');
|
||||
}
|
||||
}).catch(err => {
|
||||
console.error('Error updating Host 9 config:', err);
|
||||
alert('An error occurred while updating the Host 9 config.');
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
164
test_npm_update.py
Normal file
164
test_npm_update.py
Normal file
@ -0,0 +1,164 @@
|
||||
import requests
|
||||
import json
|
||||
import mysql.connector
|
||||
import re
|
||||
import os
|
||||
from dotenv import load_dotenv
|
||||
|
||||
# Load environment variables from .env file
|
||||
load_dotenv()
|
||||
|
||||
# Assuming config.py is in the same directory or accessible via PYTHONPATH
|
||||
from config import DevelopmentConfig as app_config
|
||||
|
||||
class NginxProxyManager:
|
||||
def __init__(self, host, email, password):
|
||||
self.host = host
|
||||
self.email = email
|
||||
self.password = password
|
||||
self.token = None
|
||||
|
||||
def login(self):
|
||||
url = f"{self.host}/api/tokens"
|
||||
payload = {
|
||||
"identity": self.email,
|
||||
"secret": self.password
|
||||
}
|
||||
headers = {
|
||||
"Content-Type": "application/json"
|
||||
}
|
||||
response = requests.post(url, headers=headers, data=json.dumps(payload))
|
||||
if response.status_code == 200:
|
||||
self.token = response.json()["token"]
|
||||
print("Login successful.")
|
||||
else:
|
||||
print(f"Failed to login: {response.text}")
|
||||
exit(1)
|
||||
|
||||
def get_proxy_host(self, host_id):
|
||||
if not self.token:
|
||||
self.login()
|
||||
|
||||
url = f"{self.host}/api/nginx/proxy-hosts/{host_id}"
|
||||
headers = {
|
||||
"Authorization": f"Bearer {self.token}"
|
||||
}
|
||||
response = requests.get(url, headers=headers)
|
||||
if response.status_code == 200:
|
||||
return response.json()
|
||||
else:
|
||||
print(f"Failed to get proxy host {host_id}: {response.text}")
|
||||
return None
|
||||
|
||||
def update_proxy_host_config(self, host_id, config):
|
||||
if not self.token:
|
||||
self.login()
|
||||
|
||||
url = f"{self.host}/api/nginx/proxy-hosts/{host_id}"
|
||||
|
||||
original_host_data = self.get_proxy_host(host_id)
|
||||
if not original_host_data:
|
||||
return
|
||||
|
||||
# Construct a new payload with only the allowed fields for an update
|
||||
update_payload = {
|
||||
"domain_names": original_host_data.get("domain_names", []),
|
||||
"forward_scheme": original_host_data.get("forward_scheme", "http"),
|
||||
"forward_host": original_host_data.get("forward_host"),
|
||||
"forward_port": original_host_data.get("forward_port"),
|
||||
"access_list_id": original_host_data.get("access_list_id", 0),
|
||||
"certificate_id": original_host_data.get("certificate_id", 0),
|
||||
"ssl_forced": original_host_data.get("ssl_forced", False),
|
||||
"hsts_enabled": original_host_data.get("hsts_enabled", False),
|
||||
"hsts_subdomains": original_host_data.get("hsts_subdomains", False),
|
||||
"http2_support": original_host_data.get("http2_support", False),
|
||||
"block_exploits": original_host_data.get("block_exploits", False),
|
||||
"caching_enabled": original_host_data.get("caching_enabled", False),
|
||||
"allow_websocket_upgrade": original_host_data.get("allow_websocket_upgrade", False),
|
||||
"advanced_config": config, # The updated advanced config
|
||||
"meta": original_host_data.get("meta", {}),
|
||||
"locations": original_host_data.get("locations", []),
|
||||
}
|
||||
|
||||
headers = {
|
||||
"Authorization": f"Bearer {self.token}",
|
||||
"Content-Type": "application/json"
|
||||
}
|
||||
|
||||
response = requests.put(url, headers=headers, data=json.dumps(update_payload))
|
||||
if response.status_code == 200:
|
||||
print(f"Successfully updated proxy host {host_id}")
|
||||
else:
|
||||
print(f"Failed to update proxy host {host_id}: {response.text}")
|
||||
|
||||
|
||||
def get_streams_from_db(db_host, db_user, db_pass, db_name, db_port):
|
||||
try:
|
||||
conn = mysql.connector.connect(
|
||||
host=db_host,
|
||||
user=db_user,
|
||||
password=db_pass,
|
||||
database=db_name,
|
||||
port=db_port
|
||||
)
|
||||
cursor = conn.cursor(dictionary=True)
|
||||
cursor.execute("""
|
||||
SELECT DISTINCT
|
||||
SUBSTRING_INDEX(stream, ' ', 1) AS streamName,
|
||||
streamURL
|
||||
FROM userAccounts
|
||||
""")
|
||||
streams = cursor.fetchall()
|
||||
cursor.close()
|
||||
conn.close()
|
||||
return streams
|
||||
except mysql.connector.Error as err:
|
||||
print(f"Error connecting to database: {err}")
|
||||
return []
|
||||
|
||||
def update_config_with_streams(config, streams):
|
||||
# Get all stream names from the database
|
||||
db_stream_names = {stream['streamName'] for stream in streams}
|
||||
|
||||
# Find all location blocks in the config
|
||||
location_blocks = re.findall(r'location ~ \^/(\w+)\(\.\*\)\$ \{[^}]+\}', config)
|
||||
|
||||
# Remove location blocks that are not in the database
|
||||
for stream_name in location_blocks:
|
||||
if stream_name not in db_stream_names:
|
||||
print(f"Removing location block for stream: {stream_name}")
|
||||
pattern = re.compile(f'location ~ \\^/{re.escape(stream_name)}\\(\\.\\*\\)\\$ {{[^}}]+}}\\s*', re.DOTALL)
|
||||
config = pattern.sub('', config)
|
||||
|
||||
# Update existing stream URLs
|
||||
for stream in streams:
|
||||
stream_name = stream['streamName']
|
||||
stream_url = stream['streamURL']
|
||||
if stream_url: # Ensure there is a URL to update to
|
||||
# Use a more specific regex to avoid replacing parts of other URLs
|
||||
pattern = re.compile(f'(location ~ \\^/{re.escape(stream_name)}\\(\\.\\*\\)\\$ {{\\s*return 302 )([^;]+)(;\\s*}})')
|
||||
config = pattern.sub(f'\\1{stream_url}/$1$is_args$args\\3', config)
|
||||
|
||||
return config
|
||||
|
||||
def main():
|
||||
npm = NginxProxyManager(app_config.NPM_HOST, app_config.NPM_EMAIL, app_config.NPM_PASSWORD)
|
||||
npm.login()
|
||||
host = npm.get_proxy_host(9)
|
||||
if host:
|
||||
current_config = host.get('advanced_config', '')
|
||||
print("Current Config:")
|
||||
print(current_config)
|
||||
|
||||
streams = get_streams_from_db(app_config.DBHOST, app_config.DBUSER, app_config.DBPASS, app_config.DATABASE, app_config.DBPORT)
|
||||
if streams:
|
||||
new_config = update_config_with_streams(current_config, streams)
|
||||
print("\nNew Config:")
|
||||
print(new_config)
|
||||
|
||||
# Uncomment the following line to apply the changes
|
||||
npm.update_proxy_host_config(9, new_config)
|
||||
print("\nTo apply the changes, uncomment the last line in the main function.")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
Loading…
x
Reference in New Issue
Block a user