"""
MailOS Web Application Module.
This module provides a web interface for managing email monitoring configurations
using PyWebIO.
"""
import logging
import uuid
from dataclasses import dataclass
from typing import Dict, List, Optional
from pywebio import start_server
from pywebio.output import clear, put_button, put_markdown, toast, use_scope
from pywebio.pin import pin
from mailos.check_emails import init_scheduler
from mailos.check_emails import main as check_emails_main
from mailos.ui.checker_form import create_checker_form
from mailos.ui.display import display_checkers, refresh_display
from mailos.utils.config_utils import load_config, save_config
from mailos.utils.logger_utils import logger
from mailos.vendors.config import VENDOR_CONFIGS
scheduler = None
[docs]
@dataclass
class CheckerConfig:
"""Email checker configuration."""
id: str
name: str
monitor_email: str
password: str
imap_server: str
imap_port: int
llm_provider: str
model: str
system_prompt: str
enabled_tools: List[str]
enabled: bool
auto_reply: bool
last_run: str = "Never"
[docs]
def to_dict(self) -> Dict:
"""Convert to dictionary for storage."""
return {
"id": self.id,
"name": self.name,
"monitor_email": self.monitor_email,
"password": self.password,
"imap_server": self.imap_server,
"imap_port": self.imap_port,
"llm_provider": self.llm_provider,
"model": self.model,
"system_prompt": self.system_prompt,
"enabled_tools": self.enabled_tools,
"enabled": self.enabled,
"auto_reply": self.auto_reply,
"last_run": self.last_run,
}
[docs]
def update_vendor_credentials(checker_dict: Dict, vendor_config) -> None:
"""Update vendor-specific credentials in checker dictionary."""
if not vendor_config:
return
for field in vendor_config.fields:
if hasattr(pin, field.name):
field_value = getattr(pin, field.name)
if field_value or field.required:
checker_dict[field.name] = field_value
[docs]
def save_checker(identifier: Optional[str] = None) -> None:
"""Save or update an email checker configuration."""
try:
config = load_config()
logger.info(f"Updating checker with identifier: {identifier}")
# Create checker config from form data
checker_config = CheckerConfig.from_form(identifier)
logger.debug(f"Enabled tools from form: {checker_config.enabled_tools}")
if identifier:
# Update existing checker
for checker in config["checkers"]:
if checker.get("id") == identifier:
# Convert to dictionary and update
checker_dict = checker_config.to_dict()
# Add vendor-specific credentials
update_vendor_credentials(
checker_dict, VENDOR_CONFIGS.get(checker_config.llm_provider)
)
# Preserve last_run if exists
if "last_run" in checker:
checker_dict["last_run"] = checker["last_run"]
# Update checker
checker.update(checker_dict)
logger.info(f"Updated checker with ID: {identifier}")
break
else:
logger.warning(f"No checker found with ID: {identifier}")
return
else:
# Create new checker dictionary
new_checker = checker_config.to_dict()
# Add vendor-specific credentials
update_vendor_credentials(
new_checker, VENDOR_CONFIGS.get(checker_config.llm_provider)
)
# Add to config
config["checkers"].append(new_checker)
logger.info(f"Added new checker with ID: {new_checker['id']}")
# Save configuration
save_config(config)
# Update UI
clear("edit_checker_form" if identifier else "new_checker_form")
refresh_display()
toast(f"Checker {'updated' if identifier else 'added'} successfully")
# Run initial check for new checkers
if not identifier:
check_emails_main()
toast("Initial check completed")
except Exception as e:
logger.error(f"Failed to save configuration: {str(e)}")
toast(f"Failed to save configuration: {str(e)}", color="error")
[docs]
def ensure_checker_ids(config: Dict) -> bool:
"""Ensure all checkers have IDs."""
modified = False
for checker in config.get("checkers", []):
if not checker.get("id"):
checker["id"] = str(uuid.uuid4())
modified = True
return modified
[docs]
def check_email_app():
"""Run the main web application."""
global scheduler
if not scheduler:
logger.info("Initializing scheduler...")
scheduler = init_scheduler()
put_markdown("# MailOS")
put_markdown("Configure multiple email accounts to monitor")
config = load_config()
# Ensure all checkers have IDs
if ensure_checker_ids(config):
save_config(config)
logger.info("Added IDs to existing checkers")
with use_scope("checkers"):
display_checkers(config, save_checker)
put_button(
"Add New Checker", onclick=lambda: create_checker_form(on_save=save_checker)
)
[docs]
def cli():
"""CLI entry point for the application."""
import click
@click.command()
@click.option(
"--log-level",
type=click.Choice(
["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"], case_sensitive=False
),
default="INFO",
help="Set the logging level",
)
def run(log_level: str):
"""Run the web application with specified log level."""
# Set log level on root logger
logging.getLogger().setLevel(getattr(logging, log_level.upper()))
logger.debug("Starting application with log level: %s", log_level)
init_scheduler()
start_server(check_email_app, port=8080, debug=True)
run()
if __name__ == "__main__":
cli()