feat: Files-Seite verbessert - View, Download & Delete für alle Dateitypen
UI Improvements: - Agent Work Files: View-, Download- und Delete-Buttons hinzugefügt - Projektdokumente: Download- und Delete-Buttons hinzugefügt - Konsistentes UI über alle Datei-Kategorien - View-Modal für Agent-Dateien (wie Projektdokumente) Backend: - /files/agent/<agent_key>/view/<filename> - Agent-Datei anzeigen - /files/agent/<agent_key>/delete/<filename> - Agent-Datei löschen - /files/agent/<agent_key>/<filename>?download=1 - Force Download - /files/project/<filename>?download=1 - Projektdatei Download - /files/project/delete/<filename> - Projektdatei löschen Security: - Path traversal protection für alle Routes - Whitelist-basierte Dateityp-Validierung - Agent-Zugriff nur auf eigene work-Verzeichnisse Features: - 👁 View: Datei im Modal anzeigen (Markdown, TXT) - ↓ Download: Force download statt Browser-Ansicht - ✕ Delete: Datei löschen mit Bestätigung
This commit is contained in:
parent
73c36785e2
commit
11352d2ca5
2 changed files with 124 additions and 2 deletions
112
app.py
112
app.py
|
|
@ -1951,7 +1951,66 @@ def download_agent_file(agent_key, filename):
|
|||
if not os.path.isfile(filepath):
|
||||
return jsonify({'error': 'Datei nicht gefunden'}), 404
|
||||
|
||||
return send_from_directory(work_dir, filename, as_attachment=False)
|
||||
# Force download wenn download=1 Parameter
|
||||
as_attachment = request.args.get('download') == '1'
|
||||
return send_from_directory(work_dir, filename, as_attachment=as_attachment)
|
||||
|
||||
|
||||
@app.route('/files/agent/<agent_key>/view/<filename>')
|
||||
def view_agent_file(agent_key, filename):
|
||||
"""Gibt Inhalt einer Agent-Datei als JSON zurück."""
|
||||
if agent_key not in AGENTS:
|
||||
return jsonify({'error': 'Agent nicht gefunden'}), 404
|
||||
|
||||
dirs = ensure_agent_structure(agent_key)
|
||||
work_dir = dirs['work_dir']
|
||||
filepath = os.path.join(work_dir, filename)
|
||||
|
||||
# Security: Stelle sicher, dass die Datei im work_dir ist
|
||||
if not os.path.abspath(filepath).startswith(os.path.abspath(work_dir)):
|
||||
return jsonify({'error': 'Zugriff verweigert'}), 403
|
||||
|
||||
if not os.path.isfile(filepath):
|
||||
return jsonify({'error': 'Datei nicht gefunden'}), 404
|
||||
|
||||
try:
|
||||
with open(filepath, 'r', encoding='utf-8') as f:
|
||||
content = f.read()
|
||||
|
||||
if request.args.get('json'):
|
||||
return jsonify({'content': content})
|
||||
return content
|
||||
except Exception as e:
|
||||
return jsonify({'error': str(e)}), 500
|
||||
|
||||
|
||||
@app.route('/files/agent/<agent_key>/delete/<filename>')
|
||||
def delete_agent_file(agent_key, filename):
|
||||
"""Löscht eine Datei aus dem Work-Ordner eines Agenten."""
|
||||
if agent_key not in AGENTS:
|
||||
flash('Agent nicht gefunden', 'danger')
|
||||
return redirect(url_for('files_page'))
|
||||
|
||||
dirs = ensure_agent_structure(agent_key)
|
||||
work_dir = dirs['work_dir']
|
||||
filepath = os.path.join(work_dir, filename)
|
||||
|
||||
# Security: Stelle sicher, dass die Datei im work_dir ist
|
||||
if not os.path.abspath(filepath).startswith(os.path.abspath(work_dir)):
|
||||
flash('Zugriff verweigert', 'danger')
|
||||
return redirect(url_for('files_page'))
|
||||
|
||||
if not os.path.isfile(filepath):
|
||||
flash('Datei nicht gefunden', 'warning')
|
||||
return redirect(url_for('files_page'))
|
||||
|
||||
try:
|
||||
os.remove(filepath)
|
||||
flash(f'Agent-Datei "{filename}" gelöscht', 'success')
|
||||
except Exception as e:
|
||||
flash(f'Fehler beim Löschen: {str(e)}', 'danger')
|
||||
|
||||
return redirect(url_for('files_page'))
|
||||
|
||||
|
||||
@app.route('/files/email/view/<filename>')
|
||||
|
|
@ -2032,6 +2091,57 @@ def view_project_file(filename):
|
|||
return jsonify({'error': str(e)}), 500
|
||||
|
||||
|
||||
@app.route('/files/project/<filename>')
|
||||
def download_project_file(filename):
|
||||
"""Liefert eine Projektdatei zum Download."""
|
||||
base_dir = os.path.dirname(__file__)
|
||||
filepath = os.path.join(base_dir, filename)
|
||||
|
||||
# Security: stay in base dir
|
||||
if os.path.dirname(os.path.abspath(filepath)) != os.path.abspath(base_dir):
|
||||
return jsonify({'error': 'Zugriff verweigert'}), 403
|
||||
|
||||
allowed_ext = ('.md', '.txt', '.docx')
|
||||
if not filename.lower().endswith(allowed_ext):
|
||||
return jsonify({'error': 'Dateityp nicht unterstützt'}), 400
|
||||
|
||||
if not os.path.isfile(filepath):
|
||||
return jsonify({'error': 'Datei nicht gefunden'}), 404
|
||||
|
||||
# Force download wenn download=1 Parameter
|
||||
as_attachment = request.args.get('download') == '1'
|
||||
return send_from_directory(base_dir, filename, as_attachment=as_attachment)
|
||||
|
||||
|
||||
@app.route('/files/project/delete/<filename>')
|
||||
def delete_project_file(filename):
|
||||
"""Löscht eine Projektdatei."""
|
||||
base_dir = os.path.dirname(__file__)
|
||||
filepath = os.path.join(base_dir, filename)
|
||||
|
||||
# Security: stay in base dir
|
||||
if os.path.dirname(os.path.abspath(filepath)) != os.path.abspath(base_dir):
|
||||
flash('Zugriff verweigert', 'danger')
|
||||
return redirect(url_for('files_page'))
|
||||
|
||||
allowed_ext = ('.md', '.txt', '.docx')
|
||||
if not filename.lower().endswith(allowed_ext):
|
||||
flash('Dateityp nicht unterstützt', 'warning')
|
||||
return redirect(url_for('files_page'))
|
||||
|
||||
if not os.path.isfile(filepath):
|
||||
flash('Datei nicht gefunden', 'warning')
|
||||
return redirect(url_for('files_page'))
|
||||
|
||||
try:
|
||||
os.remove(filepath)
|
||||
flash(f'Projektdokument "{filename}" gelöscht', 'success')
|
||||
except Exception as e:
|
||||
flash(f'Fehler beim Löschen: {str(e)}', 'danger')
|
||||
|
||||
return redirect(url_for('files_page'))
|
||||
|
||||
|
||||
@app.route('/emails', methods=['GET', 'POST'])
|
||||
def emails():
|
||||
"""Email Management Interface"""
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue