Add FTP/SFTP support and tree-view folder picker with lazy loading
FTP/SFTP processor: - New ftp_processor.py with adapter pattern for FTP (passive) and SFTP - Same design as smb_processor: read PDFs, forward via SMTP, move to processed - Eingangs-/Ausgangsbelege with separate paths, modes (forward/separator) - paramiko==3.5.0 for SFTP support - Schema v9 with new ftp_* settings - Integrated in scheduler Tree-view folder picker (SMB + FTP): - Reusable tree rendering from flat path lists - Expandable/collapsible nodes with toggle arrows - Lazy loading: only top-level folders on open, sub-folders on-demand - Auto-expand ancestors of currently selected value (with preload) - Reload button stays for manual refresh - Always fresh load when opening picker - New endpoints: /api/list-smb-subfolders, /api/list-ftp-subfolders FTP-specific fixes: - list_pdfs uses LIST instead of NLST (more reliable across servers) - Stateful CWD bug fixed in ensure_dir/stat_exists/rename (previously created /Buch/Buch/X instead of /Buch/X due to CWD drift) - All operations reset CWD via _reset_cwd() before stateful calls - _resolve() helper for SFTP to handle empty path / chroot users Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
+53
-1
@@ -15,7 +15,8 @@ from app.database import init_db, get_settings, save_settings, get_log_entries,
|
||||
from app.mail_processor import process_mailbox, send_test_email, test_imap_connection, create_imap_folder
|
||||
from app.scheduler import start_scheduler, configure_job, get_scheduler_status
|
||||
from app.scanner import process_scanned_pdf, generate_separator_pdf, UPLOAD_DIR
|
||||
from app.smb_processor import process_smb_share, test_smb_connection, create_smb_folder, list_smb_folders
|
||||
from app.smb_processor import process_smb_share, test_smb_connection, create_smb_folder, list_smb_folders, list_smb_subfolders
|
||||
from app.ftp_processor import process_ftp, test_ftp_connection, create_ftp_folder, list_ftp_folders, list_ftp_subfolders
|
||||
from app.amazon_processor import (
|
||||
start_login as amazon_start_login,
|
||||
submit_otp as amazon_submit_otp,
|
||||
@@ -122,6 +123,18 @@ async def _save_form_settings(request: Request) -> dict:
|
||||
"smb_source_path_ausgang": form.get("smb_source_path_ausgang", ""),
|
||||
"smb_processed_path_ausgang": form.get("smb_processed_path_ausgang", ""),
|
||||
"smb_mode": form.get("smb_mode", "forward"),
|
||||
# FTP / SFTP
|
||||
"ftp_enabled": form.get("ftp_enabled", "false"),
|
||||
"ftp_protocol": form.get("ftp_protocol", "sftp"),
|
||||
"ftp_server": form.get("ftp_server", ""),
|
||||
"ftp_port": form.get("ftp_port", "22"),
|
||||
"ftp_username": form.get("ftp_username", ""),
|
||||
"ftp_password": form.get("ftp_password") or current.get("ftp_password", ""),
|
||||
"ftp_source_path": form.get("ftp_source_path", ""),
|
||||
"ftp_processed_path": form.get("ftp_processed_path", "Verarbeitet"),
|
||||
"ftp_source_path_ausgang": form.get("ftp_source_path_ausgang", ""),
|
||||
"ftp_processed_path_ausgang": form.get("ftp_processed_path_ausgang", ""),
|
||||
"ftp_mode": form.get("ftp_mode", "forward"),
|
||||
# Debug
|
||||
"debug_save_amazon_pdfs": form.get("debug_save_amazon_pdfs", "false"),
|
||||
}
|
||||
@@ -219,6 +232,45 @@ async def api_create_smb_folder(request: Request):
|
||||
return JSONResponse(result)
|
||||
|
||||
|
||||
@app.get("/api/list-smb-subfolders")
|
||||
async def api_list_smb_subfolders(request: Request):
|
||||
parent = request.query_params.get("path", "")
|
||||
result = await list_smb_subfolders(parent)
|
||||
return JSONResponse(result)
|
||||
|
||||
|
||||
@app.post("/api/test-ftp")
|
||||
async def api_test_ftp(request: Request):
|
||||
await _save_form_settings(request)
|
||||
result = await test_ftp_connection()
|
||||
return JSONResponse(result)
|
||||
|
||||
|
||||
@app.post("/api/process-ftp")
|
||||
async def api_process_ftp(request: Request):
|
||||
await _save_form_settings(request)
|
||||
result = await process_ftp()
|
||||
return JSONResponse(result)
|
||||
|
||||
|
||||
@app.post("/api/create-ftp-folder")
|
||||
async def api_create_ftp_folder(request: Request):
|
||||
body = await request.json()
|
||||
folder_name = body.get("folder_name", "")
|
||||
result = await create_ftp_folder(folder_name)
|
||||
if result["success"]:
|
||||
folders_result = await list_ftp_folders()
|
||||
result["folders"] = folders_result.get("folders", [])
|
||||
return JSONResponse(result)
|
||||
|
||||
|
||||
@app.get("/api/list-ftp-subfolders")
|
||||
async def api_list_ftp_subfolders(request: Request):
|
||||
parent = request.query_params.get("path", "")
|
||||
result = await list_ftp_subfolders(parent)
|
||||
return JSONResponse(result)
|
||||
|
||||
|
||||
@app.get("/log", response_class=HTMLResponse)
|
||||
async def log_page(request: Request):
|
||||
logs = await get_log_entries(limit=500)
|
||||
|
||||
Reference in New Issue
Block a user