-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathapp.py
More file actions
219 lines (186 loc) · 8 KB
/
Copy pathapp.py
File metadata and controls
219 lines (186 loc) · 8 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
import os
import mimetypes
from datetime import datetime, timedelta
from flask import Flask, session, redirect, request, jsonify, render_template as render
from werkzeug.exceptions import NotFound
from werkzeug.middleware.proxy_fix import ProxyFix
# Import der Blueprints (Modularisierte Route-Handler)
from blueprints.auth import auth_bp
from blueprints.dashboard import dashboard_bp
from blueprints.board import board_bp
from blueprints.files import files_bp
from blueprints.api import api_bp
from intelligence_worker.ai import ai_bp
from blueprints.design import design_bp
from blueprints.chat_log import chat_log_bp
from blueprints.react_ui import react_ui_bp
from blueprints.api_sentinel import api_sentinel_bp
from blueprints.vps import vps_bp
from blueprints.git_log import git_log_bp
from blueprints.extended_stats import extended_bp
from blueprints.api.grow_api import grow_api_bp
from blueprints.api.sentinel_api import sentinel_api_bp
from blueprints.api.storage_api import storage_api_bp
from blueprints.api.system_api import system_api_bp
from core.config import Config
def create_app():
app = Flask(__name__)
app.wsgi_app = ProxyFix(app.wsgi_app, x_for=1, x_proto=1)
mimetypes.add_type('image/webp', '.webp')
app.config['SEND_FILE_MAX_AGE_DEFAULT'] = timedelta(days=7)
app.config.from_object(Config)
Config.validate()
app.permanent_session_lifetime = timedelta(hours=8)
@app.errorhandler(Exception)
def handle_exception(e):
# We don't want to log 404s as errors unless it's unusual
if not isinstance(e, NotFound):
app.logger.error(f"Unhandled Exception: {str(e)}")
# If it's a 404, we return a proper 404 JSON for API calls
if isinstance(e, NotFound):
if request.path.startswith('/api/'):
return jsonify({
'ok': False,
'error': 'Not Found',
'details': f"Der Endpunkt '{request.path}' existiert nicht."
}), 404
return render('404.html'), 404
if request.path.startswith('/api/'):
import traceback
error_details = traceback.format_exc() if app.debug else 'Interner Serverfehler'
return jsonify({
'ok': False,
'error': 'Interner Serverfehler',
'details': error_details
}), 500
return render('500.html', error=str(e)), 500
from core.db import db
db.init_app(app)
with app.app_context():
from core.models import Project, Task, Changelog, Note, ChatSession, User
db.create_all()
from core.migrate import migrate_json_to_db
try:
migrate_json_to_db()
except Exception as e:
print(f'Migration error: {e}')
from core.ai_agent import start_agent, start_json_agent
from intelligence_worker.pigeon_engine import pigeon_scanner
try:
start_agent(app)
start_json_agent(app)
pigeon_scanner.start_background_monitoring(app)
except Exception as e:
print(f'AI Agent / P.I.G.E.O.N. start error: {e}')
@app.template_global()
def now():
return datetime.now()
@app.template_global()
def asset_version(filename):
try:
static_root = os.path.join(app.root_path, 'static')
return int(os.path.getmtime(os.path.join(static_root, filename)))
except OSError:
return int(datetime.now().timestamp())
from core.utils import load_projects
@app.context_processor
def inject_sidebar():
projects = load_projects()
n8n_proj = next((p for p in projects if p['id'] == 'n8n-ai-projects'), None)
return dict(sidebar_projects=projects, sidebar_n8n_project=n8n_proj)
@app.before_request
def check_banned_ips():
from core.utils import safe_load_json
from datetime import datetime
# 1. Whitelist-Check
whitelist = safe_load_json(Config.WHITELIST_IPS_FILE, default=['127.0.0.1'])
if request.remote_addr in whitelist:
return None
# 2. Ban-Check (with Expiry)
banned_data = safe_load_json(Config.BANNED_IPS_FILE, default=[])
now = datetime.now().isoformat()
for entry in banned_data:
if isinstance(entry, dict) and entry.get('ip') == request.remote_addr:
expiry = entry.get('expiry')
if not expiry or expiry > now:
return jsonify({"ok": False, "error": "BANNED", "details": f"IP gesperrt bis {expiry if expiry else 'unendlich'}."}), 403
break # Expired entries are handled by P.I.G.E.O.N. scan
# 3. Simple Rate Limiting (Exposed for Analytics)
app.rate_limiter_data = {}
@app.before_request
def rate_limit_middleware():
from time import time
from core.utils import safe_load_json
# Exclude static assets and UI entry points
if (request.path.startswith('/static/')
or request.path.startswith('/ui/')
or request.path == '/favicon.ico'
or request.path == '/health'):
return None
ip = request.remote_addr
# Whitelist & Local Network Bypass
whitelist = safe_load_json(Config.WHITELIST_IPS_FILE, default=['127.0.0.1'])
if ip in whitelist or ip.startswith('192.168.') or ip.startswith('10.') or ip.startswith('172.'):
return None
now = time()
window = 60 # 1 minute
limit = 200 # 200 requests per minute
history = app.rate_limiter_data.get(ip, [])
history = [t for t in history if now - t < window]
if len(history) >= limit:
return jsonify({"ok": False, "error": "RATE_LIMIT", "details": "Zu viele Anfragen. Bitte warten."}), 429
history.append(now)
app.rate_limiter_data[ip] = history
_PASS_THROUGH = ('/health', '/login', '/logout', '/ui', '/ui/', '/favicon.ico')
@app.before_request
def redirect_old_ui():
p = request.path
if p in ('/ui/login', '/ui/login/'):
return redirect('/login')
# Only redirect if it's the root or a known old path that needs migration
# This prevents redirect loops for unknown assets or sub-paths
if p == '/':
return redirect('/ui/')
return None
app.register_blueprint(auth_bp)
app.register_blueprint(dashboard_bp)
app.register_blueprint(board_bp)
app.register_blueprint(files_bp)
app.register_blueprint(api_bp)
app.register_blueprint(ai_bp)
app.register_blueprint(design_bp)
app.register_blueprint(chat_log_bp)
app.register_blueprint(react_ui_bp)
app.register_blueprint(api_sentinel_bp)
app.register_blueprint(vps_bp)
app.register_blueprint(git_log_bp)
app.register_blueprint(extended_bp)
app.register_blueprint(grow_api_bp)
app.register_blueprint(sentinel_api_bp)
app.register_blueprint(storage_api_bp)
app.register_blueprint(system_api_bp)
return app
app = create_app()
@app.route('/health')
def health():
from flask import jsonify
from core.db import db
from sqlalchemy import text
import shutil
health_status = {'status': 'ok', 'timestamp': datetime.now().isoformat(), 'checks': {}}
try:
db.session.execute(text('SELECT 1')).scalar()
health_status['checks']['database'] = 'connected'
except Exception as e:
health_status['status'] = 'error'
health_status['checks']['database'] = f'failed: {str(e)}'
try:
du = shutil.disk_usage('/')
free_gb = du.free / (1024**3)
health_status['checks']['storage'] = f'{free_gb:.1f} GB free'
if free_gb < 1.0: health_status['status'] = 'degraded'
except Exception: health_status['checks']['storage'] = 'unknown'
code = 200 if health_status['status'] != 'error' else 500
return jsonify(health_status), code
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000, debug=False)