feat: add Uptime Kuma integration with periodic ping task and configuration options
This commit is contained in:
+4
-2
@@ -86,12 +86,12 @@ def list_files_api(request: Request, db: Session = Depends(get_db)):
|
||||
# API endpoints
|
||||
@router.get("/diagnostic/settings")
|
||||
@require_login
|
||||
async def diagnostic_settings(current_user: dict = Depends(get_current_user)):
|
||||
async def diagnostic_settings(request: Request, current_user: dict = Depends(get_current_user)):
|
||||
"""
|
||||
API endpoint to dump settings to the log and view basic config information
|
||||
This endpoint doesn't expose sensitive information like passwords or tokens
|
||||
"""
|
||||
from app.utils.config_validator import dump_all_settings
|
||||
from app.utils.config_validator import dump_all_settings, get_settings_for_display
|
||||
# Dump full settings to log for admin to see
|
||||
dump_all_settings()
|
||||
|
||||
@@ -107,6 +107,8 @@ async def diagnostic_settings(current_user: dict = Depends(get_current_user)):
|
||||
"sftp": bool(getattr(settings, 'sftp_host', None)),
|
||||
"paperless": bool(getattr(settings, 'paperless_host', None)),
|
||||
"google_drive": bool(getattr(settings, 'google_drive_credentials_json', None)),
|
||||
"uptime_kuma": bool(getattr(settings, 'uptime_kuma_url', None)),
|
||||
"auth": bool(getattr(settings, 'authentik_config_url', None)),
|
||||
},
|
||||
"imap_enabled": bool(getattr(settings, 'imap1_host', None) or getattr(settings, 'imap2_host', None)),
|
||||
}
|
||||
|
||||
+11
-1
@@ -30,6 +30,7 @@ from app.tasks.upload_to_email import upload_to_email
|
||||
|
||||
from app.tasks.imap_tasks import pull_all_inboxes
|
||||
from app.tasks.send_to_all import send_to_all_destinations
|
||||
from app.tasks.uptime_kuma_tasks import ping_uptime_kuma
|
||||
|
||||
celery.conf.task_routes = {
|
||||
"app.tasks.*": {"queue": "default"},
|
||||
@@ -47,4 +48,13 @@ celery.conf.beat_schedule = {
|
||||
"task": "app.tasks.imap_tasks.pull_all_inboxes",
|
||||
"schedule": crontab(minute="*/1"), # every 1 minute
|
||||
},
|
||||
}
|
||||
# Add Uptime Kuma ping task if configured
|
||||
"ping-uptime-kuma": {
|
||||
"task": "app.tasks.uptime_kuma_tasks.ping_uptime_kuma",
|
||||
"schedule": crontab(minute=f"*/{settings.uptime_kuma_ping_interval}"),
|
||||
"options": {"expires": 55}, # Ensure tasks don't pile up
|
||||
} if settings.uptime_kuma_url else None,
|
||||
}
|
||||
|
||||
# Remove None entries from beat_schedule
|
||||
celery.conf.beat_schedule = {k: v for k, v in celery.conf.beat_schedule.items() if v is not None}
|
||||
+33
-1
@@ -1,7 +1,8 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
from pydantic_settings import BaseSettings
|
||||
from typing import Optional
|
||||
from typing import Optional, List, Dict, Any
|
||||
import os
|
||||
|
||||
class Settings(BaseSettings):
|
||||
database_url: str
|
||||
@@ -10,6 +11,7 @@ class Settings(BaseSettings):
|
||||
openai_base_url: str = "https://api.openai.com/v1" # Default to OpenAI's endpoint
|
||||
openai_model: str = "gpt-4o-mini" # Default model
|
||||
workdir: str
|
||||
debug: bool = False # Default to False
|
||||
|
||||
# Making Dropbox optional
|
||||
dropbox_app_key: Optional[str] = None
|
||||
@@ -110,7 +112,37 @@ class Settings(BaseSettings):
|
||||
s3_storage_class: Optional[str] = "STANDARD" # Default storage class
|
||||
s3_acl: Optional[str] = "private" # Default ACL
|
||||
|
||||
# Uptime Kuma settings
|
||||
uptime_kuma_url: Optional[str] = None
|
||||
uptime_kuma_ping_interval: int = 5 # Default ping interval in minutes
|
||||
|
||||
# Get version from file or environment
|
||||
@property
|
||||
def version(self) -> str:
|
||||
# First try to get version from environment
|
||||
env_version = os.environ.get("APP_VERSION")
|
||||
if env_version:
|
||||
return env_version
|
||||
|
||||
# Then try to get version from VERSION file
|
||||
version_file = os.path.join(os.path.dirname(os.path.dirname(__file__)), "VERSION")
|
||||
if os.path.exists(version_file):
|
||||
with open(version_file, "r") as f:
|
||||
return f.read().strip()
|
||||
|
||||
# Default version if not found
|
||||
return "0.1.0-dev"
|
||||
|
||||
class Config:
|
||||
env_file = ".env"
|
||||
# Convert string representations of booleans to actual booleans
|
||||
@classmethod
|
||||
def parse_env_var(cls, field_name: str, raw_val: str) -> Any:
|
||||
if field_name.endswith('_enabled') or field_name == 'debug':
|
||||
if raw_val.lower() in ('false', '0', 'no', 'n', 'f'):
|
||||
return False
|
||||
if raw_val.lower() in ('true', '1', 'yes', 'y', 't'):
|
||||
return True
|
||||
return raw_val
|
||||
|
||||
settings = Settings()
|
||||
|
||||
+4
-4
@@ -76,10 +76,10 @@ async def status_dashboard(request: Request):
|
||||
async def env_debug(request: Request):
|
||||
"""
|
||||
Debug endpoint to view environment variables and settings
|
||||
Only shows values when DEBUG is True
|
||||
Uses actual debug setting from config
|
||||
"""
|
||||
# Default DEBUG to True for this route
|
||||
debug_enabled = True
|
||||
# Use the actual debug setting from configuration
|
||||
debug_enabled = settings.debug
|
||||
|
||||
# Get settings data
|
||||
from app.utils.config_validator import get_settings_for_display
|
||||
@@ -91,7 +91,7 @@ async def env_debug(request: Request):
|
||||
"request": request,
|
||||
"settings": settings_data,
|
||||
"debug_enabled": debug_enabled,
|
||||
"app_version": getattr(settings, 'version', 'Unknown')
|
||||
"app_version": settings.version
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@@ -68,6 +68,10 @@ def upload_to_sftp(file_path: str):
|
||||
remote_base = settings.sftp_folder or ""
|
||||
remote_path = extract_remote_path(file_path, settings.workdir, remote_base)
|
||||
|
||||
# Ensure the remote path starts with a slash if the base folder does
|
||||
if remote_base.startswith('/') and not remote_path.startswith('/'):
|
||||
remote_path = '/' + remote_path
|
||||
|
||||
# Function to check if file exists in SFTP server
|
||||
def check_exists_in_sftp(path):
|
||||
try:
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import logging
|
||||
import requests
|
||||
from celery import shared_task
|
||||
|
||||
from app.config import settings
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@shared_task
|
||||
def ping_uptime_kuma():
|
||||
"""
|
||||
Periodic Celery task that pings the configured Uptime Kuma URL
|
||||
to report that the document processor service is running.
|
||||
If no URL is configured, the task does nothing.
|
||||
"""
|
||||
if not settings.uptime_kuma_url:
|
||||
logger.debug("Uptime Kuma URL not configured, skipping ping")
|
||||
return
|
||||
|
||||
try:
|
||||
logger.info(f"Pinging Uptime Kuma at {settings.uptime_kuma_url}")
|
||||
response = requests.get(settings.uptime_kuma_url, timeout=10)
|
||||
response.raise_for_status()
|
||||
logger.info(f"Successfully pinged Uptime Kuma: {response.status_code}")
|
||||
return True
|
||||
except requests.exceptions.RequestException as e:
|
||||
logger.error(f"Failed to ping Uptime Kuma: {e}")
|
||||
return False
|
||||
+301
-149
@@ -129,170 +129,322 @@ def validate_storage_configs():
|
||||
onedrive_issues.append("OneDrive credentials are not fully configured")
|
||||
issues['onedrive'] = onedrive_issues
|
||||
|
||||
# Validate Uptime Kuma
|
||||
uptime_kuma_issues = []
|
||||
if not getattr(settings, 'uptime_kuma_url', None):
|
||||
uptime_kuma_issues.append("UPTIME_KUMA_URL is not configured")
|
||||
issues['uptime_kuma'] = uptime_kuma_issues
|
||||
|
||||
return issues
|
||||
|
||||
def get_provider_status():
|
||||
"""Get the status of each provider for the dashboard"""
|
||||
providers = {
|
||||
"Email": {
|
||||
"configured": bool(getattr(settings, 'email_host', None) and
|
||||
getattr(settings, 'email_username', None) and
|
||||
getattr(settings, 'email_password', None)),
|
||||
"icon": "mail",
|
||||
"url": getattr(settings, 'email_host', None) or "",
|
||||
"description": f"Send to {getattr(settings, 'email_default_recipient', 'Not configured')}"
|
||||
},
|
||||
"Dropbox": {
|
||||
"configured": bool(getattr(settings, 'dropbox_app_key', None) and
|
||||
getattr(settings, 'dropbox_app_secret', None) and
|
||||
getattr(settings, 'dropbox_refresh_token', None)),
|
||||
"icon": "dropbox",
|
||||
"url": "https://dropbox.com",
|
||||
"description": f"Upload to folder: {getattr(settings, 'dropbox_folder', 'Root')}"
|
||||
},
|
||||
"Nextcloud": {
|
||||
"configured": bool(getattr(settings, 'nextcloud_upload_url', None) and
|
||||
getattr(settings, 'nextcloud_username', None)),
|
||||
"icon": "cloud",
|
||||
"url": getattr(settings, 'nextcloud_upload_url', "").split('/remote.php')[0] if getattr(settings, 'nextcloud_upload_url', None) else "",
|
||||
"description": f"Upload to folder: {getattr(settings, 'nextcloud_folder', 'Root')}"
|
||||
},
|
||||
"SFTP": {
|
||||
"configured": bool(getattr(settings, 'sftp_host', None) and
|
||||
getattr(settings, 'sftp_username', None) and
|
||||
(getattr(settings, 'sftp_password', None) or getattr(settings, 'sftp_private_key', None))),
|
||||
"icon": "server",
|
||||
"url": f"sftp://{getattr(settings, 'sftp_host', '')}:{getattr(settings, 'sftp_port', 22)}",
|
||||
"description": f"Upload to {getattr(settings, 'sftp_host', 'Not configured')}:{getattr(settings, 'sftp_folder', '/')}"
|
||||
},
|
||||
"Paperless": {
|
||||
"configured": bool(getattr(settings, 'paperless_host', None) and
|
||||
getattr(settings, 'paperless_ngx_api_token', None)),
|
||||
"icon": "file-text",
|
||||
"url": getattr(settings, 'paperless_host', ""),
|
||||
"description": "Document management system"
|
||||
},
|
||||
"S3": {
|
||||
"configured": bool(getattr(settings, 's3_bucket_name', None) and
|
||||
getattr(settings, 'aws_access_key_id', None)),
|
||||
"icon": "database",
|
||||
"url": f"https://s3.console.aws.amazon.com/s3/buckets/{getattr(settings, 's3_bucket_name', '')}",
|
||||
"description": f"Bucket: {getattr(settings, 's3_bucket_name', 'Not configured')}"
|
||||
},
|
||||
"FTP": {
|
||||
"configured": bool(getattr(settings, 'ftp_host', None) and
|
||||
getattr(settings, 'ftp_username', None)),
|
||||
"icon": "hard-drive",
|
||||
"url": f"ftp://{getattr(settings, 'ftp_host', '')}:{getattr(settings, 'ftp_port', 21)}",
|
||||
"description": f"Upload to {getattr(settings, 'ftp_host', 'Not configured')}:{getattr(settings, 'ftp_folder', '/')}"
|
||||
},
|
||||
"WebDAV": {
|
||||
"configured": bool(getattr(settings, 'webdav_url', None) and
|
||||
getattr(settings, 'webdav_username', None)),
|
||||
"icon": "globe",
|
||||
"url": getattr(settings, 'webdav_url', ""),
|
||||
"description": f"Upload to {getattr(settings, 'webdav_folder', '/')}"
|
||||
},
|
||||
"Google Drive": {
|
||||
"configured": bool(getattr(settings, 'google_drive_credentials_json', None)),
|
||||
"icon": "google",
|
||||
"url": "https://drive.google.com",
|
||||
"description": f"Folder ID: {getattr(settings, 'google_drive_folder_id', 'Not configured')}"
|
||||
},
|
||||
"OneDrive": {
|
||||
"configured": bool(getattr(settings, 'onedrive_client_id', None) and
|
||||
getattr(settings, 'onedrive_refresh_token', None)),
|
||||
"icon": "microsoft",
|
||||
"url": "https://onedrive.live.com",
|
||||
"description": f"Upload to folder: {getattr(settings, 'onedrive_folder_path', 'Not configured')}"
|
||||
"""Returns status information for all configured providers"""
|
||||
providers = {}
|
||||
|
||||
# Check Dropbox configuration
|
||||
providers["Dropbox"] = {
|
||||
"name": "Dropbox",
|
||||
"configured": bool(getattr(settings, 'dropbox_refresh_token', None)),
|
||||
"enabled": True,
|
||||
"details": {
|
||||
"folder": getattr(settings, 'dropbox_folder', 'Not set')
|
||||
}
|
||||
}
|
||||
|
||||
# Check Paperless configuration
|
||||
providers["Paperless-ngx"] = {
|
||||
"name": "Paperless-ngx",
|
||||
"configured": bool(getattr(settings, 'paperless_host', None) and
|
||||
getattr(settings, 'paperless_ngx_api_token', None)),
|
||||
"enabled": True,
|
||||
"details": {
|
||||
"host": getattr(settings, 'paperless_host', 'Not set')
|
||||
}
|
||||
}
|
||||
|
||||
# Check NextCloud configuration
|
||||
providers["NextCloud"] = {
|
||||
"name": "NextCloud",
|
||||
"configured": bool(getattr(settings, 'nextcloud_upload_url', None) and
|
||||
getattr(settings, 'nextcloud_username', None) and
|
||||
getattr(settings, 'nextcloud_password', None)),
|
||||
"enabled": True,
|
||||
"details": {
|
||||
"url": getattr(settings, 'nextcloud_upload_url', 'Not set'),
|
||||
"folder": getattr(settings, 'nextcloud_folder', 'Not set')
|
||||
}
|
||||
}
|
||||
|
||||
# Check SFTP configuration
|
||||
providers["SFTP Storage"] = {
|
||||
"name": "SFTP Storage",
|
||||
"configured": bool(getattr(settings, 'sftp_host', None) and
|
||||
getattr(settings, 'sftp_username', None) and
|
||||
(getattr(settings, 'sftp_password', None) or
|
||||
getattr(settings, 'sftp_private_key', None))),
|
||||
"enabled": True,
|
||||
"details": {
|
||||
"host": getattr(settings, 'sftp_host', 'Not set'),
|
||||
"folder": getattr(settings, 'sftp_folder', 'Not set')
|
||||
}
|
||||
}
|
||||
|
||||
# Check S3 configuration
|
||||
providers["S3 Storage"] = {
|
||||
"name": "S3 Storage",
|
||||
"configured": bool(getattr(settings, 's3_bucket_name', None) and
|
||||
getattr(settings, 'aws_access_key_id', None) and
|
||||
getattr(settings, 'aws_secret_access_key', None)),
|
||||
"enabled": True,
|
||||
"details": {
|
||||
"bucket": getattr(settings, 's3_bucket_name', 'Not set'),
|
||||
"region": getattr(settings, 'aws_region', 'Not set')
|
||||
}
|
||||
}
|
||||
|
||||
# Check Google Drive configuration
|
||||
providers["Google Drive"] = {
|
||||
"name": "Google Drive",
|
||||
"configured": bool(getattr(settings, 'google_drive_credentials_json', None) and
|
||||
getattr(settings, 'google_drive_folder_id', None)),
|
||||
"enabled": True,
|
||||
"details": {
|
||||
"folder_id": getattr(settings, 'google_drive_folder_id', 'Not set'),
|
||||
"delegate": getattr(settings, 'google_drive_delegate_to', 'Not set')
|
||||
}
|
||||
}
|
||||
|
||||
# Check OneDrive configuration
|
||||
providers["OneDrive"] = {
|
||||
"name": "OneDrive",
|
||||
"configured": bool(getattr(settings, 'onedrive_client_id', None) and
|
||||
getattr(settings, 'onedrive_client_secret', None) and
|
||||
getattr(settings, 'onedrive_refresh_token', None)),
|
||||
"enabled": True,
|
||||
"details": {
|
||||
"folder": getattr(settings, 'onedrive_folder_path', 'Not set')
|
||||
}
|
||||
}
|
||||
|
||||
# Check WebDAV configuration
|
||||
providers["WebDAV"] = {
|
||||
"name": "WebDAV",
|
||||
"configured": bool(getattr(settings, 'webdav_url', None) and
|
||||
getattr(settings, 'webdav_username', None) and
|
||||
getattr(settings, 'webdav_password', None)),
|
||||
"enabled": True,
|
||||
"details": {
|
||||
"url": getattr(settings, 'webdav_url', 'Not set'),
|
||||
"folder": getattr(settings, 'webdav_folder', 'Not set')
|
||||
}
|
||||
}
|
||||
|
||||
return providers
|
||||
|
||||
def dump_all_settings():
|
||||
"""Dump all settings to the log for debugging"""
|
||||
logger.info("================ SETTINGS DUMP ================")
|
||||
|
||||
# Get all attributes from settings object
|
||||
attributes = inspect.getmembers(settings, lambda a: not inspect.isroutine(a))
|
||||
settings_dict = {a[0]: a[1] for a in attributes
|
||||
if not a[0].startswith('_') and not callable(a[1])}
|
||||
|
||||
# Sort keys for better readability
|
||||
for key in sorted(settings_dict.keys()):
|
||||
value = settings_dict[key]
|
||||
# Hide sensitive values
|
||||
if any(sensitive in key.lower() for sensitive in ['password', 'secret', 'token', 'key']):
|
||||
if value:
|
||||
value = "******** [HIDDEN FOR SECURITY]"
|
||||
logger.info(f" {key} = {value}")
|
||||
|
||||
# Also log all environment variables
|
||||
logger.info("----------- ENVIRONMENT VARIABLES -----------")
|
||||
env_vars_to_log = {}
|
||||
for key in sorted(os.environ.keys()):
|
||||
value = os.environ[key]
|
||||
# Hide sensitive values
|
||||
if any(sensitive in key.lower() for sensitive in ['password', 'secret', 'token', 'key']):
|
||||
if value:
|
||||
value = "******** [HIDDEN FOR SECURITY]"
|
||||
env_vars_to_log[key] = value
|
||||
|
||||
for key in sorted(env_vars_to_log.keys()):
|
||||
logger.info(f" {key} = {env_vars_to_log[key]}")
|
||||
|
||||
logger.info("=============================================")
|
||||
"""Log all settings values for diagnostic purposes"""
|
||||
logger.info("--- DUMPING ALL SETTINGS FOR DIAGNOSTIC PURPOSES ---")
|
||||
for key in dir(settings):
|
||||
if not key.startswith('_') and not callable(getattr(settings, key)):
|
||||
value = getattr(settings, key)
|
||||
# Mask sensitive values in logs
|
||||
if key.lower().find('password') >= 0 or key.lower().find('secret') >= 0 or key.lower().find('token') >= 0 or key.lower().find('key') >= 0:
|
||||
if value:
|
||||
value = "********"
|
||||
logger.info(f"{key}: {value}")
|
||||
logger.info("--- END OF SETTINGS DUMP ---")
|
||||
|
||||
def get_settings_for_display(show_values=False):
|
||||
"""Get all settings organized by category for display in UI"""
|
||||
# Get all attributes from settings object
|
||||
attributes = inspect.getmembers(settings, lambda a: not inspect.isroutine(a))
|
||||
settings_dict = {a[0]: a[1] for a in attributes
|
||||
if not a[0].startswith('_') and not callable(a[1])}
|
||||
"""
|
||||
Group settings into logical categories and check if they are configured.
|
||||
Returns a dictionary with categories as keys and lists of setting items as values.
|
||||
Each setting item is a dict with name, value, and is_configured.
|
||||
|
||||
# Categorize settings
|
||||
categories = {
|
||||
"Core": [],
|
||||
"Email": [],
|
||||
"IMAP": [],
|
||||
"Storage": [],
|
||||
"Authentication": [],
|
||||
"Integration": [],
|
||||
"Other": []
|
||||
If show_values is False, sensitive values are masked.
|
||||
"""
|
||||
# First include system info with version in result
|
||||
result = {
|
||||
"System Info": [
|
||||
{
|
||||
"name": "App Version",
|
||||
"value": settings.version,
|
||||
"is_configured": True
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
# Sort keys for better readability
|
||||
for key in sorted(settings_dict.keys()):
|
||||
value = settings_dict[key]
|
||||
# Mask sensitive values if show_values is False
|
||||
display_value = value
|
||||
if not show_values or any(sensitive in key.lower() for sensitive in ['password', 'secret', 'token', 'key']):
|
||||
if value:
|
||||
display_value = "******** [HIDDEN]"
|
||||
else:
|
||||
display_value = None
|
||||
|
||||
# Categorize by key prefix
|
||||
setting_item = {"name": key, "value": display_value, "is_configured": value is not None and value != ""}
|
||||
|
||||
if key.startswith(('email_', 'smtp_')):
|
||||
categories["Email"].append(setting_item)
|
||||
elif key.startswith('imap'):
|
||||
categories["IMAP"].append(setting_item)
|
||||
elif key.startswith(('s3_', 'aws_', 'dropbox_', 'nextcloud_', 'sftp_', 'ftp_', 'google_drive_')):
|
||||
categories["Storage"].append(setting_item)
|
||||
elif key.startswith(('auth_', 'jwt_', 'oauth_')):
|
||||
categories["Authentication"].append(setting_item)
|
||||
elif key.startswith(('paperless_', 'tesseract_', 'azure_')):
|
||||
categories["Integration"].append(setting_item)
|
||||
elif key in ('workdir', 'external_hostname', 'debug', 'version', 'env', 'log_level'):
|
||||
categories["Core"].append(setting_item)
|
||||
else:
|
||||
categories["Other"].append(setting_item)
|
||||
# Define categories and their settings
|
||||
categories = {
|
||||
"Core": [
|
||||
"debug", # Explicitly include debug setting
|
||||
"external_hostname",
|
||||
"workdir",
|
||||
"database_url",
|
||||
"redis_url",
|
||||
"gotenberg_url"
|
||||
],
|
||||
"Authentication": [
|
||||
"auth_enabled",
|
||||
"authentik_client_id",
|
||||
"authentik_client_secret",
|
||||
"authentik_config_url"
|
||||
],
|
||||
"Email": [
|
||||
"email_host",
|
||||
"email_port",
|
||||
"email_username",
|
||||
"email_password",
|
||||
"email_use_tls",
|
||||
"email_sender",
|
||||
"email_default_recipient"
|
||||
],
|
||||
"IMAP": [
|
||||
"imap1_host",
|
||||
"imap1_port",
|
||||
"imap1_username",
|
||||
"imap1_password",
|
||||
"imap1_ssl",
|
||||
"imap1_poll_interval_minutes",
|
||||
"imap1_delete_after_process",
|
||||
"imap2_host",
|
||||
"imap2_port",
|
||||
"imap2_username",
|
||||
"imap2_password",
|
||||
"imap2_ssl",
|
||||
"imap2_poll_interval_minutes",
|
||||
"imap2_delete_after_process"
|
||||
],
|
||||
"Dropbox": [
|
||||
"dropbox_app_key",
|
||||
"dropbox_app_secret",
|
||||
"dropbox_folder",
|
||||
"dropbox_refresh_token"
|
||||
],
|
||||
"NextCloud": [
|
||||
"nextcloud_upload_url",
|
||||
"nextcloud_username",
|
||||
"nextcloud_password",
|
||||
"nextcloud_folder"
|
||||
],
|
||||
"Paperless": [
|
||||
"paperless_host",
|
||||
"paperless_ngx_api_token"
|
||||
],
|
||||
"Google Drive": [
|
||||
"google_drive_credentials_json",
|
||||
"google_drive_folder_id",
|
||||
"google_drive_delegate_to"
|
||||
],
|
||||
"OneDrive": [
|
||||
"onedrive_client_id",
|
||||
"onedrive_client_secret",
|
||||
"onedrive_tenant_id",
|
||||
"onedrive_refresh_token",
|
||||
"onedrive_folder_path"
|
||||
],
|
||||
"WebDAV": [
|
||||
"webdav_url",
|
||||
"webdav_username",
|
||||
"webdav_password",
|
||||
"webdav_folder",
|
||||
"webdav_verify_ssl"
|
||||
],
|
||||
"SFTP": [
|
||||
"sftp_host",
|
||||
"sftp_port",
|
||||
"sftp_username",
|
||||
"sftp_password",
|
||||
"sftp_folder",
|
||||
"sftp_private_key",
|
||||
"sftp_private_key_passphrase"
|
||||
],
|
||||
"FTP": [
|
||||
"ftp_host",
|
||||
"ftp_port",
|
||||
"ftp_username",
|
||||
"ftp_password",
|
||||
"ftp_folder"
|
||||
],
|
||||
"S3/AWS": [
|
||||
"aws_access_key_id",
|
||||
"aws_secret_access_key",
|
||||
"aws_region",
|
||||
"s3_bucket_name",
|
||||
"s3_folder_prefix",
|
||||
"s3_storage_class",
|
||||
"s3_acl"
|
||||
],
|
||||
"AI Services": [
|
||||
"openai_api_key",
|
||||
"openai_base_url",
|
||||
"openai_model",
|
||||
"azure_ai_key",
|
||||
"azure_endpoint",
|
||||
"azure_region"
|
||||
],
|
||||
"Monitoring": [
|
||||
"uptime_kuma_url",
|
||||
"uptime_kuma_ping_interval"
|
||||
]
|
||||
}
|
||||
|
||||
# Remove empty categories
|
||||
return {k: v for k, v in categories.items() if v}
|
||||
# Handle any settings that don't fit into the predefined categories
|
||||
all_settings = set([key for key in dir(settings)
|
||||
if not key.startswith('_') and
|
||||
not callable(getattr(settings, key)) and
|
||||
key not in ["model_computed_fields", "model_config",
|
||||
"model_extra", "model_fields",
|
||||
"model_fields_set"]])
|
||||
|
||||
# Ensure 'version' is excluded since we display it separately
|
||||
all_settings.discard("version")
|
||||
|
||||
categorized_settings = set()
|
||||
for cat_settings in categories.values():
|
||||
categorized_settings.update(cat_settings)
|
||||
|
||||
uncategorized = all_settings - categorized_settings
|
||||
if uncategorized:
|
||||
categories["Other"] = list(uncategorized)
|
||||
|
||||
# Build the result
|
||||
for category, setting_keys in categories.items():
|
||||
items = []
|
||||
for key in setting_keys:
|
||||
if hasattr(settings, key):
|
||||
value = getattr(settings, key)
|
||||
|
||||
# List of patterns that indicate sensitive values
|
||||
sensitive_patterns = [
|
||||
'password', 'secret', 'token', 'api_key', 'private_key',
|
||||
'credentials', 'access_key', 'auth'
|
||||
]
|
||||
|
||||
# Check if this is a sensitive value that should be masked
|
||||
is_sensitive = any(pattern in key.lower() for pattern in sensitive_patterns)
|
||||
|
||||
# Mask sensitive values regardless of debug mode
|
||||
# Other values are only hidden if debug mode is off AND show_values is False
|
||||
if (is_sensitive or not show_values) and value:
|
||||
if is_sensitive:
|
||||
value = "********"
|
||||
|
||||
# Check if the setting is configured (has a non-None value)
|
||||
# For boolean settings, consider them configured even if False
|
||||
is_configured = value is not None
|
||||
if is_configured and isinstance(value, str):
|
||||
is_configured = len(value) > 0
|
||||
|
||||
items.append({
|
||||
"name": key,
|
||||
"value": value,
|
||||
"is_configured": is_configured
|
||||
})
|
||||
|
||||
if items: # Only add categories that have items
|
||||
result[category] = items
|
||||
|
||||
return result
|
||||
|
||||
def check_all_configs():
|
||||
"""Run all configuration validations and log results"""
|
||||
|
||||
+31
-27
@@ -94,38 +94,42 @@ def sanitize_filename(filename):
|
||||
|
||||
return sanitized
|
||||
|
||||
def extract_remote_path(local_path, base_dir, remote_base=None):
|
||||
def extract_remote_path(file_path, base_dir, remote_base=""):
|
||||
"""
|
||||
Extracts the appropriate remote path based on a local path structure.
|
||||
Extract a remote path for a file by preserving its directory structure
|
||||
relative to the base directory, but with a new remote base path.
|
||||
|
||||
Args:
|
||||
local_path (str): The local file path
|
||||
base_dir (str): The local base directory to remove from path
|
||||
remote_base (str, optional): Remote base directory to prepend
|
||||
|
||||
Returns:
|
||||
str: The calculated remote path
|
||||
Modified to skip 'processed' directory in the remote path.
|
||||
"""
|
||||
# Convert both paths to use forward slashes for consistency
|
||||
local_path = local_path.replace('\\', '/')
|
||||
base_dir = base_dir.replace('\\', '/')
|
||||
# Normalize paths for consistent handling across platforms
|
||||
file_path = os.path.normpath(file_path)
|
||||
base_dir = os.path.normpath(base_dir)
|
||||
|
||||
# Make sure base_dir ends with a slash
|
||||
if not base_dir.endswith('/'):
|
||||
base_dir += '/'
|
||||
|
||||
# Remove the base directory from the local path
|
||||
if local_path.startswith(base_dir):
|
||||
relative_path = local_path[len(base_dir):]
|
||||
# Get relative path from base directory
|
||||
if file_path.startswith(base_dir):
|
||||
rel_path = os.path.relpath(file_path, base_dir)
|
||||
else:
|
||||
# If local_path is not within base_dir, just use the filename
|
||||
relative_path = os.path.basename(local_path)
|
||||
# If not a subdirectory of base_dir, just use the filename
|
||||
rel_path = os.path.basename(file_path)
|
||||
|
||||
# Prepend the remote base if provided
|
||||
# Skip 'processed' directory if it's in the path
|
||||
path_parts = rel_path.split(os.sep)
|
||||
if 'processed' in path_parts:
|
||||
# Remove 'processed' from the path
|
||||
path_parts.remove('processed')
|
||||
rel_path = os.path.join(*path_parts)
|
||||
|
||||
# Combine with remote base path
|
||||
if remote_base:
|
||||
# Ensure remote_base ends with slash
|
||||
if not remote_base.endswith('/'):
|
||||
remote_base += '/'
|
||||
return remote_base + relative_path
|
||||
if remote_base.startswith('/'):
|
||||
# Handle absolute path for services like Dropbox
|
||||
remote_path = os.path.join(remote_base[1:], rel_path)
|
||||
else:
|
||||
remote_path = os.path.join(remote_base, rel_path)
|
||||
else:
|
||||
remote_path = rel_path
|
||||
|
||||
return relative_path
|
||||
# Convert to forward slashes for compatibility with most cloud services
|
||||
remote_path = remote_path.replace(os.sep, '/')
|
||||
|
||||
return remote_path
|
||||
|
||||
Reference in New Issue
Block a user