wip
This commit is contained in:
parent
a4ce8a291d
commit
950d72aba1
8 changed files with 375 additions and 22 deletions
138
app.py
138
app.py
|
@ -1,39 +1,144 @@
|
|||
from flask import Flask, render_template, request, jsonify
|
||||
from flask import Flask, render_template, request, jsonify, abort
|
||||
import requests
|
||||
import threading
|
||||
import os
|
||||
import jwt
|
||||
import logging
|
||||
import json
|
||||
from datetime import datetime
|
||||
from dotenv import load_dotenv
|
||||
|
||||
# Load environment variables
|
||||
load_dotenv()
|
||||
|
||||
# Configuration
|
||||
API_KEY = os.getenv('API_KEY') # Must match agent configuration
|
||||
DEBUG_MODE = os.getenv('DEBUG_MODE', 'false').lower() == 'true'
|
||||
CADDYFILE_PATH = os.getenv('CADDYFILE_PATH') # Optional - for direct file reading
|
||||
|
||||
# Setup logging
|
||||
logging.basicConfig(
|
||||
level=logging.DEBUG if DEBUG_MODE else logging.INFO,
|
||||
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
|
||||
)
|
||||
logger = logging.getLogger('caddy-dashboard')
|
||||
|
||||
# Determine if we should use local Caddyfile reading
|
||||
USE_LOCAL_CADDYFILE = CADDYFILE_PATH and os.path.exists(CADDYFILE_PATH)
|
||||
|
||||
app = Flask(__name__)
|
||||
app.config['SECRET_KEY'] = os.urandom(24)
|
||||
|
||||
# Global data storage
|
||||
proxy_data = {}
|
||||
deleted_servers = set()
|
||||
server_last_seen = {} # Track when servers were last updated
|
||||
|
||||
def verify_token(token):
|
||||
"""Verify the JWT token from an agent"""
|
||||
if not API_KEY:
|
||||
logger.error("API_KEY is not configured - authentication disabled")
|
||||
return None
|
||||
|
||||
try:
|
||||
return jwt.decode(token, API_KEY, algorithms=['HS256'])
|
||||
except jwt.ExpiredSignatureError:
|
||||
logger.warning("Authentication token has expired")
|
||||
return None
|
||||
except jwt.InvalidTokenError as e:
|
||||
logger.warning(f"Invalid authentication token: {e}")
|
||||
return None
|
||||
|
||||
def parse_local_caddyfile():
|
||||
"""Parse a local Caddyfile if LOCAL_MODE is enabled"""
|
||||
if not CADDYFILE_PATH or not os.path.exists(CADDYFILE_PATH):
|
||||
logger.error(f"Local Caddyfile not found at {CADDYFILE_PATH}")
|
||||
return {}
|
||||
|
||||
# Import here to avoid circular imports
|
||||
import re
|
||||
|
||||
entries = {}
|
||||
try:
|
||||
with open(CADDYFILE_PATH, "r") as file:
|
||||
content = file.read()
|
||||
|
||||
pattern = re.compile(r"(?P<domains>[^\s{]+(?:,\s*[^\s{]+)*)\s*{.*?reverse_proxy\s+(?P<target>https?:\/\/[\d\.]+:\d+|[\d\.]+:\d+).*?}", re.DOTALL)
|
||||
matches = pattern.findall(content)
|
||||
|
||||
for domains, target in matches:
|
||||
for domain in domains.split(", "):
|
||||
domain = domain.strip()
|
||||
if domain:
|
||||
entries[domain] = target.strip()
|
||||
|
||||
logger.info(f"Found {len(entries)} domain entries in local Caddyfile")
|
||||
except Exception as e:
|
||||
logger.error(f"Error parsing local Caddyfile: {e}")
|
||||
|
||||
return entries
|
||||
|
||||
@app.route('/')
|
||||
def index():
|
||||
filtered_data = {k: v for k, v in proxy_data.items() if k not in deleted_servers}
|
||||
return render_template('index.html', proxies=filtered_data)
|
||||
"""Render the dashboard homepage"""
|
||||
data_to_display = {k: v for k, v in proxy_data.items() if k not in deleted_servers}
|
||||
|
||||
# Add local Caddyfile data if available
|
||||
if USE_LOCAL_CADDYFILE:
|
||||
local_data = parse_local_caddyfile()
|
||||
if local_data:
|
||||
data_to_display["Local Server"] = local_data
|
||||
|
||||
# Add last seen timestamps
|
||||
timestamps = {server: server_last_seen.get(server, "Never") for server in data_to_display}
|
||||
|
||||
return render_template('index.html', proxies=data_to_display, timestamps=timestamps)
|
||||
|
||||
@app.route('/update', methods=['POST'])
|
||||
@app.route('/api/update', methods=['POST'])
|
||||
def update():
|
||||
"""Secure API endpoint for agents to update their data"""
|
||||
# Verify authentication
|
||||
auth_header = request.headers.get('Authorization')
|
||||
if not auth_header or not auth_header.startswith('Bearer '):
|
||||
logger.warning("Missing or invalid Authorization header")
|
||||
return jsonify({"error": "Authentication required"}), 401
|
||||
|
||||
token = auth_header.split(' ')[1]
|
||||
payload = verify_token(token)
|
||||
|
||||
if not payload:
|
||||
return jsonify({"error": "Invalid authentication token"}), 401
|
||||
|
||||
# Verify the server in the token matches the data
|
||||
data = request.json
|
||||
if not data or "server" not in data or "entries" not in data:
|
||||
return jsonify({"error": "Invalid data"}), 400
|
||||
|
||||
return jsonify({"error": "Invalid data format"}), 400
|
||||
|
||||
if payload.get('server') != data["server"]:
|
||||
logger.warning(f"Server mismatch: {payload.get('server')} vs {data['server']}")
|
||||
return jsonify({"error": "Server authentication mismatch"}), 403
|
||||
|
||||
# Update the data
|
||||
server_name = data["server"]
|
||||
proxy_data[server_name] = data["entries"]
|
||||
|
||||
server_last_seen[server_name] = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
||||
|
||||
if server_name in deleted_servers:
|
||||
deleted_servers.remove(server_name)
|
||||
|
||||
|
||||
logger.info(f"Updated data for server: {server_name} with {len(data['entries'])} entries")
|
||||
return jsonify({"message": "Updated successfully"}), 200
|
||||
|
||||
@app.route('/delete', methods=['POST'])
|
||||
def delete_entry():
|
||||
"""Delete a server from the dashboard"""
|
||||
data = request.json
|
||||
server_name = data.get("server")
|
||||
if not server_name or server_name not in proxy_data:
|
||||
return jsonify({"error": "Server not found"}), 400
|
||||
|
||||
deleted_servers.add(server_name)
|
||||
logger.info(f"Server {server_name} marked as deleted")
|
||||
return jsonify({"message": f"Server {server_name} deleted"}), 200
|
||||
|
||||
@app.route('/status/<domain>')
|
||||
|
@ -46,4 +151,19 @@ def check_status(domain):
|
|||
return jsonify({"status": "offline"})
|
||||
|
||||
if __name__ == '__main__':
|
||||
app.run(host='0.0.0.0', port=5000)
|
||||
if USE_LOCAL_CADDYFILE:
|
||||
logger.info(f"Local Caddyfile found at {CADDYFILE_PATH} - will display its data")
|
||||
# Load it initially
|
||||
local_data = parse_local_caddyfile()
|
||||
if local_data:
|
||||
proxy_data["Local Server"] = local_data
|
||||
server_last_seen["Local Server"] = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
||||
|
||||
if not API_KEY:
|
||||
logger.warning("API_KEY not set - running without authentication!")
|
||||
|
||||
# Use HTTPS in production
|
||||
if DEBUG_MODE:
|
||||
app.run(host='0.0.0.0', port=5000, debug=True)
|
||||
else:
|
||||
app.run(host='0.0.0.0', port=5000)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue