Files
nas-tools/dashboard/backend/services/email_service.py
T
Gan, Jimmy 252b94aece
Deploy Dashboard (Dev) / deploy-dev (push) Failing after 24m36s
Run Tests / Backend Tests (push) Successful in 10m13s
Run Tests / Frontend Tests (push) Failing after 22s
Run Tests / Test Summary (push) Failing after 41s
feat: complete Phase 1 MVP - add agent panel, email notifications, and PDF invoicing
Agent Panel UI:
- Visual dashboard showing all agents with status (idle/working)
- Agent cards with stats: total tasks, completed tasks
- Capabilities display for each agent
- Execution log showing recent agent activity
- Real-time updates (refreshes every 10 seconds)
- Click agent to filter execution log
- Status indicators: pending, running, completed, failed, pending_approval

Email Notifications:
- SMTP email service with HTML templates
- Task assignment notifications
- Agent approval request emails with action details
- Agent completion notifications
- Configurable via environment variables (SMTP_HOST, SMTP_USER, SMTP_PASSWORD, SMTP_TO)
- Integrated with agent executor service

PDF Invoice Generation:
- Professional PDF invoices using ReportLab
- Generate from time entries by project
- Client information and company branding
- Itemized time entries with hours, rates, amounts
- Automatic totals calculation
- Download as PDF attachment
- API endpoints: /api/opc/invoices/generate
- Projects and clients management endpoints

Additional Features:
- Projects CRUD API
- Clients CRUD API
- Project time tracking summary
- Billable vs non-billable hours tracking

Phase 1 MVP 100% Complete:
 PostgreSQL database with full schema
 Task CRUD API with automatic time tracking
 Kanban board with drag-and-drop
 Agent executor service with LLM integration
 WebSocket real-time updates
 3 core agents (PM, CTO, COO)
 Agent panel UI with execution logs
 Email notifications (Telegram + Email)
 PDF invoice generation

All 12 tasks completed!
2026-03-31 14:52:42 +08:00

156 lines
4.7 KiB
Python

"""
Email notification service for OPC
"""
import os
import smtplib
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
import logging
logger = logging.getLogger(__name__)
# Email configuration from environment
SMTP_HOST = os.getenv("SMTP_HOST", "smtp.gmail.com")
SMTP_PORT = int(os.getenv("SMTP_PORT", "587"))
SMTP_USER = os.getenv("SMTP_USER", "")
SMTP_PASSWORD = os.getenv("SMTP_PASSWORD", "")
SMTP_FROM = os.getenv("SMTP_FROM", SMTP_USER)
SMTP_TO = os.getenv("SMTP_TO", "")
async def send_email(subject: str, body: str, html_body: str = None):
"""Send email notification"""
if not SMTP_USER or not SMTP_PASSWORD or not SMTP_TO:
logger.warning("Email not configured, skipping notification")
return
try:
# Create message
msg = MIMEMultipart("alternative")
msg["Subject"] = subject
msg["From"] = SMTP_FROM
msg["To"] = SMTP_TO
# Add plain text part
msg.attach(MIMEText(body, "plain"))
# Add HTML part if provided
if html_body:
msg.attach(MIMEText(html_body, "html"))
# Send email
with smtplib.SMTP(SMTP_HOST, SMTP_PORT) as server:
server.starttls()
server.login(SMTP_USER, SMTP_PASSWORD)
server.send_message(msg)
logger.info(f"Email sent: {subject}")
except Exception as e:
logger.error(f"Failed to send email: {e}")
async def send_task_assigned_email(task: dict, assigned_to: str, assigned_type: str):
"""Send email when task is assigned"""
subject = f"Task Assigned: {task['title']}"
body = f"""You have been assigned a new task:
Title: {task['title']}
Priority: {task['priority']}
Status: {task['status']}
Description:
{task.get('description', 'No description')}
View task: https://nas.jimmygan.com/?page=opc
"""
html_body = f"""
<html>
<body>
<h2>Task Assigned</h2>
<p>You have been assigned a new task:</p>
<table style="border-collapse: collapse; margin: 20px 0;">
<tr><td style="padding: 8px; font-weight: bold;">Title:</td><td style="padding: 8px;">{task['title']}</td></tr>
<tr><td style="padding: 8px; font-weight: bold;">Priority:</td><td style="padding: 8px;">{task['priority']}</td></tr>
<tr><td style="padding: 8px; font-weight: bold;">Status:</td><td style="padding: 8px;">{task['status']}</td></tr>
</table>
<p><strong>Description:</strong></p>
<p>{task.get('description', 'No description')}</p>
<p><a href="https://nas.jimmygan.com/?page=opc" style="background-color: #4F46E5; color: white; padding: 10px 20px; text-decoration: none; border-radius: 5px; display: inline-block;">View Task</a></p>
</body>
</html>
"""
await send_email(subject, body, html_body)
async def send_agent_approval_email(agent_name: str, task: dict, reasoning: str, actions: list):
"""Send email for agent approval request"""
subject = f"Agent Approval Required: {agent_name}"
actions_text = "\n".join([f"- {action.get('type')}: {action.get('title', action.get('message', 'N/A'))}" for action in actions])
body = f"""Agent approval required:
Agent: {agent_name}
Task: {task['title']}
Reasoning:
{reasoning}
Proposed Actions:
{actions_text}
Please review and approve in the OPC dashboard:
https://nas.jimmygan.com/?page=opc
"""
html_body = f"""
<html>
<body>
<h2>🤖 Agent Approval Required</h2>
<p><strong>Agent:</strong> {agent_name}</p>
<p><strong>Task:</strong> {task['title']}</p>
<h3>Reasoning:</h3>
<p>{reasoning}</p>
<h3>Proposed Actions:</h3>
<ul>
{"".join([f"<li><strong>{action.get('type')}</strong>: {action.get('title', action.get('message', 'N/A'))}</li>" for action in actions])}
</ul>
<p><a href="https://nas.jimmygan.com/?page=opc" style="background-color: #4F46E5; color: white; padding: 10px 20px; text-decoration: none; border-radius: 5px; display: inline-block;">Review & Approve</a></p>
</body>
</html>
"""
await send_email(subject, body, html_body)
async def send_agent_completion_email(agent_name: str, task: dict, actions_count: int):
"""Send email when agent completes task"""
subject = f"Agent Task Completed: {agent_name}"
body = f"""Agent has completed a task:
Agent: {agent_name}
Task: {task['title']}
Actions Executed: {actions_count}
View details: https://nas.jimmygan.com/?page=opc
"""
html_body = f"""
<html>
<body>
<h2>✅ Agent Task Completed</h2>
<p><strong>Agent:</strong> {agent_name}</p>
<p><strong>Task:</strong> {task['title']}</p>
<p><strong>Actions Executed:</strong> {actions_count}</p>
<p><a href="https://nas.jimmygan.com/?page=opc" style="background-color: #10B981; color: white; padding: 10px 20px; text-decoration: none; border-radius: 5px; display: inline-block;">View Details</a></p>
</body>
</html>
"""
await send_email(subject, body, html_body)