Darkweb007's picture
Initial commit: Employee Offboarding Security Agent
d89fa5f
Raw
History Blame Contribute Delete
46.1 kB
import gradio as gr
import json
import os
import random
import string
from datetime import date, datetime
# ---------------------------------------------------------------------------
# Pre-computed demo scenarios (no API key needed)
# ---------------------------------------------------------------------------
TODAY = date.today().strftime("%B %d, %Y")
SCENARIOS = {
"Alex Turner β€” Senior Engineer": {
"employee": "Alex Turner",
"role": "Senior Engineer",
"dept": "Engineering",
"last_day": TODAY,
"manager": "Sarah Chen",
"offboarding_id": "OB-20240615-7842",
"risk_level": "MEDIUM",
"files_transferred": 847,
"systems": ["Google Workspace", "Slack", "GitHub", "Google Drive"],
"timeline": [
("T+0min", "Offboarding initiated by HR", "completed"),
("T+1min", "Google Workspace account suspended", "completed"),
("T+2min", "Slack account deactivated", "completed"),
("T+3min", "GitHub org membership revoked (14 repos)", "completed"),
("T+5min", "847 Google Drive files transferred to Sarah Chen","completed"),
("T+6min", "Offboarding audit report generated", "completed"),
("T+7min", "Compliance email sent to IT Security & Legal", "completed"),
],
"compliance_flags": [],
"audit_summary": (
"Alex Turner's offboarding was completed successfully with all access revoked "
"within 7 minutes of initiation. 847 Drive files were transferred to manager "
"Sarah Chen. No compliance flags were raised. The engineering credentials "
"vault has been updated and all active SSH keys have been invalidated."
),
"it_subject": "COMPLETED: Offboarding β€” Alex Turner (Senior Engineer) β€” OB-20240615-7842",
"it_body": (
"This is an automated confirmation that offboarding for Alex Turner has been "
"completed.\n\nActions completed:\nβ€’ Google Workspace: SUSPENDED\n"
"β€’ Slack: DEACTIVATED\nβ€’ GitHub: REMOVED (14 repositories)\n"
"β€’ Drive: 847 files transferred to Sarah Chen\n\n"
"Audit ID: OB-20240615-7842\nCompleted at: T+7min\n\nNo further action required."
),
"legal_subject": "Offboarding Compliance Notice β€” Alex Turner β€” OB-20240615-7842",
"legal_body": (
"For compliance records: Employee Alex Turner (Senior Engineer, Engineering) "
"has been fully offboarded.\n\nData handling: All work files transferred to "
"authorized manager Sarah Chen.\nAccess: All systems revoked.\n"
"Risk level: MEDIUM\nCompliance flags: None\n\n"
"Audit trail is stored in the compliance database under OB-20240615-7842."
),
},
"Maria Santos β€” VP Marketing": {
"employee": "Maria Santos",
"role": "VP Marketing",
"dept": "Marketing",
"last_day": TODAY,
"manager": "CEO David Park",
"offboarding_id": "OB-20240615-7843",
"risk_level": "HIGH",
"files_transferred": 2341,
"systems": ["Google Workspace", "Slack", "GitHub", "Google Drive", "Salesforce", "Figma", "Notion"],
"extra_label": "Elevated Security Protocol",
"extra_detail": "Admin access revoked from 3 SaaS tools Β· Marketing credentials rotated",
"timeline": [
("T+0min", "HIGH-RISK offboarding initiated β€” VP-level departure", "completed"),
("T+1min", "Google Workspace admin privileges stripped", "completed"),
("T+1min", "Google Workspace account suspended", "completed"),
("T+2min", "Slack account deactivated + admin role removed", "completed"),
("T+3min", "Figma org admin revoked", "completed"),
("T+3min", "Notion workspace admin revoked", "completed"),
("T+4min", "Salesforce admin access removed", "completed"),
("T+4min", "Marketing credentials vault rotated (12 credentials)", "completed"),
("T+6min", "2,341 Google Drive files transferred to CEO David Park", "completed"),
("T+7min", "Elevated security audit report generated", "completed"),
("T+8min", "Priority compliance email sent to IT Security & Legal", "completed"),
],
"compliance_flags": [
"VP-level departure requires Board notification per policy Β§4.2",
"Marketing SaaS admin credentials rotated β€” IT to verify coverage",
],
"audit_summary": (
"Maria Santos's high-priority offboarding was completed in 8 minutes. "
"Admin access was revoked from Google Workspace, Figma, Notion, and Salesforce "
"before account suspension. 12 marketing credentials were rotated. "
"2,341 Drive files transferred to CEO David Park. Two compliance flags "
"raised for Board notification and credential rotation verification."
),
"it_subject": "URGENT COMPLETED: High-Risk Offboarding β€” Maria Santos (VP Marketing) β€” OB-20240615-7843",
"it_body": (
"HIGH-RISK offboarding for Maria Santos (VP Marketing) is complete.\n\n"
"Actions completed:\nβ€’ Google Workspace admin + account: REVOKED & SUSPENDED\n"
"β€’ Slack admin + account: REVOKED & DEACTIVATED\n"
"β€’ Figma admin: REVOKED\nβ€’ Notion admin: REVOKED\n"
"β€’ Salesforce admin: REVOKED\nβ€’ Marketing credentials: ROTATED (12 items)\n"
"β€’ Drive: 2,341 files β†’ CEO David Park\n\n"
"ACTION REQUIRED: Verify credential rotation coverage with marketing team.\n"
"Audit ID: OB-20240615-7843"
),
"legal_subject": "HIGH-RISK Offboarding Compliance β€” Maria Santos β€” OB-20240615-7843",
"legal_body": (
"COMPLIANCE NOTICE β€” HIGH RISK\n\nEmployee Maria Santos (VP Marketing) "
"has been offboarded.\n\nCompliance flags raised:\n"
"1. Board notification required per policy Β§4.2\n"
"2. IT to confirm marketing credential rotation coverage\n\n"
"Risk Level: HIGH\nAdmin systems affected: 4\n"
"Data transferred: 2,341 files to CEO David Park\n"
"Audit ID: OB-20240615-7843"
),
},
"James Kim β€” Data Analyst": {
"employee": "James Kim",
"role": "Data Analyst",
"dept": "Finance",
"last_day": TODAY,
"manager": "CFO Lisa Wong",
"offboarding_id": "OB-20240615-7844",
"risk_level": "HIGH",
"files_transferred": 1203,
"systems": ["Google Workspace", "Slack", "GitHub", "Google Drive", "AWS", "Jira"],
"extra_label": "SOX Compliance Protocol",
"extra_detail": "Finance data access audit logged Β· SOX compliance report generated",
"timeline": [
("T+0min", "Finance offboarding initiated β€” SOX compliance protocol activated", "completed"),
("T+1min", "Google Workspace account suspended", "completed"),
("T+1min", "AWS IAM user deactivated, access keys revoked", "completed"),
("T+2min", "Slack account deactivated", "completed"),
("T+2min", "GitHub org membership revoked (6 repos)", "completed"),
("T+3min", "Jira tickets reassigned to CFO Lisa Wong (23 open items)", "completed"),
("T+4min", "Finance data access audit logged to immutable SOX ledger", "completed"),
("T+5min", "1,203 Google Drive files transferred to CFO Lisa Wong", "completed"),
("T+6min", "SOX compliance report generated and signed", "completed"),
("T+7min", "Compliance email + SOX report sent to IT Security & Legal", "completed"),
],
"compliance_flags": [
"SOX audit log entry created β€” retain for 7 years per SEC Β§802",
"Finance data access history exported and sealed",
"AWS billing alerts updated to remove departed user",
],
"audit_summary": (
"James Kim's Finance-department offboarding triggered the SOX compliance protocol. "
"All access was revoked in 2 minutes. 23 open Jira tickets were reassigned to "
"CFO Lisa Wong. A full finance data access audit was logged to the immutable SOX "
"ledger. 1,203 Drive files transferred. SOX compliance report generated and "
"dispatched to Legal for 7-year retention."
),
"it_subject": "COMPLETED: SOX Offboarding β€” James Kim (Data Analyst, Finance) β€” OB-20240615-7844",
"it_body": (
"Offboarding for James Kim (Data Analyst, Finance) is complete.\n\n"
"Actions completed:\nβ€’ Google Workspace: SUSPENDED\n"
"β€’ AWS IAM: DEACTIVATED (access keys revoked)\n"
"β€’ Slack: DEACTIVATED\nβ€’ GitHub: REMOVED (6 repos)\n"
"β€’ Jira: 23 tickets reassigned to CFO Lisa Wong\n"
"β€’ Drive: 1,203 files β†’ CFO Lisa Wong\n\n"
"ACTION REQUIRED: Update AWS billing alerts.\n"
"Audit ID: OB-20240615-7844"
),
"legal_subject": "SOX Compliance Offboarding β€” James Kim β€” OB-20240615-7844",
"legal_body": (
"SOX COMPLIANCE NOTICE\n\nEmployee James Kim (Data Analyst, Finance) "
"has been offboarded under SOX compliance protocol.\n\n"
"Compliance actions:\nβ€’ Full data access audit logged to immutable SOX ledger\n"
"β€’ Finance data access history exported and sealed\n"
"β€’ SOX compliance report attached β€” retain for 7 years per SEC Β§802\n\n"
"Risk Level: HIGH\nAudit ID: OB-20240615-7844"
),
},
"Rachel Adams β€” Customer Success Manager": {
"employee": "Rachel Adams",
"role": "Customer Success Manager",
"dept": "Sales",
"last_day": TODAY,
"manager": "Tom Bradley",
"offboarding_id": "OB-20240615-7845",
"risk_level": "MEDIUM",
"files_transferred": 512,
"systems": ["Google Workspace", "Slack", "GitHub", "Google Drive", "Salesforce"],
"extra_label": "CRM Handoff Protocol",
"extra_detail": "Salesforce opportunities reassigned Β· Customer contacts notified of new rep",
"timeline": [
("T+0min", "Sales offboarding initiated β€” CRM handoff protocol activated", "completed"),
("T+1min", "Google Workspace account suspended", "completed"),
("T+2min", "Slack account deactivated", "completed"),
("T+3min", "Salesforce: 47 open opportunities reassigned to Tom Bradley", "completed"),
("T+3min", "Salesforce: 312 customer contacts updated with new rep info", "completed"),
("T+4min", "Customer notification emails sent (312 contacts)", "completed"),
("T+5min", "512 Google Drive files transferred to Tom Bradley", "completed"),
("T+6min", "CRM handoff report generated", "completed"),
("T+7min", "Compliance email sent to IT Security & Legal", "completed"),
],
"compliance_flags": [
"Customer contact notifications sent β€” log retained for GDPR compliance",
"Salesforce pipeline handoff requires VP Sales review within 48 hours",
],
"audit_summary": (
"Rachel Adams's offboarding included the full CRM handoff protocol for Sales. "
"47 open Salesforce opportunities and 312 customer contacts were reassigned to "
"manager Tom Bradley. All 312 customers received automated notification emails "
"introducing their new rep. 512 Drive files transferred. GDPR-compliant "
"notification log retained. VP Sales review flagged for within 48 hours."
),
"it_subject": "COMPLETED: Offboarding + CRM Handoff β€” Rachel Adams (CSM) β€” OB-20240615-7845",
"it_body": (
"Offboarding for Rachel Adams (Customer Success Manager, Sales) is complete.\n\n"
"Actions completed:\nβ€’ Google Workspace: SUSPENDED\nβ€’ Slack: DEACTIVATED\n"
"β€’ Salesforce: 47 opportunities β†’ Tom Bradley\n"
"β€’ Salesforce: 312 contacts updated + notified\n"
"β€’ Drive: 512 files β†’ Tom Bradley\n\n"
"ACTION REQUIRED: VP Sales to review Salesforce pipeline within 48 hours.\n"
"Audit ID: OB-20240615-7845"
),
"legal_subject": "Offboarding Compliance + GDPR Notice β€” Rachel Adams β€” OB-20240615-7845",
"legal_body": (
"COMPLIANCE NOTICE\n\nEmployee Rachel Adams (Customer Success Manager, Sales) "
"has been offboarded.\n\nGDPR Note: 312 customer notification emails sent β€” "
"log retained per GDPR Article 30 record-keeping requirements.\n\n"
"CRM handoff: 47 opportunities, 312 contacts β†’ Tom Bradley\n"
"Risk Level: MEDIUM\nAudit ID: OB-20240615-7845"
),
},
}
# ---------------------------------------------------------------------------
# Helper: render a pre-computed demo card
# ---------------------------------------------------------------------------
RISK_COLORS = {
"HIGH": ("#fca5a5", "rgba(127,29,29,0.45)"),
"MEDIUM": ("#fde68a", "rgba(120,53,15,0.40)"),
"LOW": ("#86efac", "rgba(20,83,45,0.40)"),
}
def _badge(text: str, bg: str, color: str) -> str:
return (
f'<span style="background:{bg};color:{color};padding:3px 10px;'
f'border-radius:12px;font-size:0.75rem;font-weight:700;'
f'letter-spacing:0.05em;">{text}</span>'
)
def _system_chip(name: str) -> str:
return (
f'<span style="background:rgba(99,102,241,0.25);color:#c7d2fe;'
f'border:1px solid rgba(99,102,241,0.4);padding:3px 10px;'
f'border-radius:10px;font-size:0.8rem;margin:2px;display:inline-block;">'
f'βœ… {name}</span>'
)
def render_demo_card(scenario_key: str) -> str:
s = SCENARIOS[scenario_key]
risk_color, risk_bg = RISK_COLORS.get(s["risk_level"], ("#e2e8f0", "rgba(50,50,80,0.4)"))
# --- Header ---
header = f"""
<div style="background:rgba(30,27,75,0.55);border:1px solid rgba(99,102,241,0.35);
border-radius:14px;padding:20px 24px;margin-bottom:14px;">
<div style="display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:8px;">
<div>
<div style="font-size:1.35rem;font-weight:800;color:#e2e8f0;">
πŸ”’ {s['employee']}
</div>
<div style="color:#a5b4fc;font-size:0.9rem;margin-top:4px;">
{s['role']} Β· {s['dept']} Β· Last day: {s['last_day']} Β· Manager: {s['manager']}
</div>
<div style="color:#94a3b8;font-size:0.8rem;margin-top:2px;">
Offboarding ID: <code style="color:#c7d2fe;">{s['offboarding_id']}</code>
</div>
</div>
<div style="display:flex;flex-direction:column;align-items:flex-end;gap:6px;">
{_badge('OFFBOARDING COMPLETE βœ…', 'rgba(21,128,61,0.45)', '#86efac')}
{_badge('Risk: ' + s['risk_level'], risk_bg, risk_color)}
</div>
</div>
</div>
"""
# --- Systems revoked ---
chips = "".join(_system_chip(sys) for sys in s["systems"])
systems_card = f"""
<div style="background:rgba(15,23,42,0.55);border:1px solid rgba(99,102,241,0.25);
border-radius:12px;padding:16px 20px;margin-bottom:14px;">
<div style="font-weight:700;color:#c7d2fe;margin-bottom:10px;font-size:0.95rem;">
πŸ›‘οΈ Systems Revoked / Actions Completed
</div>
<div>{chips}</div>
<div style="color:#94a3b8;font-size:0.82rem;margin-top:10px;">
πŸ“ {s['files_transferred']:,} Google Drive files transferred to {s['manager']}
</div>
</div>
"""
# --- Extra protocol badge ---
extra_html = ""
if s.get("extra_label"):
extra_html = f"""
<div style="background:rgba(234,179,8,0.15);border:1px solid rgba(234,179,8,0.35);
border-radius:10px;padding:12px 18px;margin-bottom:14px;">
<span style="color:#fde68a;font-weight:700;">⚑ {s['extra_label']}</span>
<span style="color:#fcd34d;font-size:0.85rem;margin-left:10px;">{s['extra_detail']}</span>
</div>
"""
# --- Timeline ---
tl_rows = ""
for time, action, status in s["timeline"]:
tl_rows += f"""
<tr>
<td style="color:#7c3aed;font-size:0.8rem;padding:5px 12px 5px 0;
white-space:nowrap;font-family:monospace;">{time}</td>
<td style="color:#e2e8f0;font-size:0.85rem;padding:5px 0;">
βœ… {action}
</td>
</tr>
"""
timeline_card = f"""
<div style="background:rgba(15,23,42,0.55);border:1px solid rgba(99,102,241,0.25);
border-radius:12px;padding:16px 20px;margin-bottom:14px;">
<div style="font-weight:700;color:#c7d2fe;margin-bottom:10px;font-size:0.95rem;">
⏱️ Action Timeline
</div>
<table style="border-collapse:collapse;width:100%;">{tl_rows}</table>
</div>
"""
# --- Compliance flags ---
flags_html = ""
if s["compliance_flags"]:
flags_items = "".join(
f'<li style="color:#fde68a;font-size:0.85rem;margin:4px 0;">⚠️ {f}</li>'
for f in s["compliance_flags"]
)
flags_html = f"""
<div style="background:rgba(120,53,15,0.35);border:1px solid rgba(234,179,8,0.35);
border-radius:12px;padding:14px 18px;margin-bottom:14px;">
<div style="font-weight:700;color:#fcd34d;margin-bottom:8px;">⚠️ Compliance Flags</div>
<ul style="margin:0;padding-left:16px;">{flags_items}</ul>
</div>
"""
else:
flags_html = """
<div style="background:rgba(21,128,61,0.2);border:1px solid rgba(134,239,172,0.3);
border-radius:12px;padding:12px 18px;margin-bottom:14px;">
<span style="color:#86efac;font-size:0.9rem;">βœ… No compliance flags raised</span>
</div>
"""
# --- Audit summary ---
audit_card = f"""
<div style="background:rgba(15,23,42,0.55);border:1px solid rgba(99,102,241,0.25);
border-radius:12px;padding:16px 20px;margin-bottom:14px;">
<div style="font-weight:700;color:#c7d2fe;margin-bottom:8px;font-size:0.95rem;">
πŸ“‹ Audit Report Summary
</div>
<p style="color:#cbd5e1;font-size:0.88rem;line-height:1.6;margin:0;">
{s['audit_summary']}
</p>
</div>
"""
# --- Email previews ---
def email_block(label: str, to: str, subject: str, body: str, accent: str) -> str:
body_escaped = body.replace("\n", "<br>")
return f"""
<div style="background:rgba(15,23,42,0.55);border:1px solid {accent};
border-radius:12px;padding:16px 20px;margin-bottom:12px;">
<div style="font-weight:700;color:#e2e8f0;margin-bottom:8px;font-size:0.9rem;">
πŸ“§ {label}
</div>
<div style="color:#94a3b8;font-size:0.8rem;margin-bottom:6px;">
<b style="color:#a5b4fc;">To:</b> {to}
</div>
<div style="color:#94a3b8;font-size:0.8rem;margin-bottom:10px;">
<b style="color:#a5b4fc;">Subject:</b> {subject}
</div>
<div style="background:rgba(0,0,0,0.3);border-radius:8px;padding:12px;
color:#cbd5e1;font-size:0.82rem;line-height:1.6;
font-family:monospace;white-space:pre-wrap;">{body_escaped}</div>
</div>
"""
emails_card = f"""
<div style="background:rgba(30,27,75,0.35);border:1px solid rgba(99,102,241,0.2);
border-radius:12px;padding:14px 18px;margin-bottom:14px;">
<div style="font-weight:700;color:#c7d2fe;margin-bottom:12px;font-size:0.95rem;">
πŸ“¨ Compliance Email Previews
</div>
{email_block('Email to IT Security', 'it-security@company.com', s['it_subject'], s['it_body'], 'rgba(99,102,241,0.4)')}
{email_block('Email to Legal / Compliance', 'legal@company.com', s['legal_subject'], s['legal_body'], 'rgba(239,68,68,0.4)')}
</div>
"""
return header + systems_card + extra_html + timeline_card + flags_html + audit_card + emails_card
# ---------------------------------------------------------------------------
# generate_offboarding_report β€” calls GPT-4o-mini
# ---------------------------------------------------------------------------
def generate_offboarding_report(
name: str,
email: str,
role: str,
department: str,
manager_name: str,
manager_email: str,
last_day: str,
reason: str,
extra_systems: list,
api_key: str,
) -> str:
if not api_key or not api_key.strip():
api_key = os.environ.get("OPENAI_API_KEY", "")
if not api_key:
return _error_html(
"No API Key",
"Please provide your OpenAI API key in the field above, or set the "
"<code>OPENAI_API_KEY</code> environment variable / HF Space secret.",
)
if not name.strip():
return _error_html("Missing Input", "Please enter the employee name.")
last_day_str = last_day if isinstance(last_day, str) else str(last_day)
systems_list = ["Google Workspace", "Slack", "GitHub", "Google Drive"] + (extra_systems or [])
systems_str = ", ".join(systems_list)
prompt = f"""You are an enterprise IT security agent processing an employee offboarding.
Employee details:
- Name: {name}
- Email: {email}
- Role: {role}
- Department: {department}
- Manager: {manager_name} ({manager_email})
- Last Working Day: {last_day_str}
- Reason: {reason}
- Systems to revoke: {systems_str}
Generate a realistic offboarding report. Return ONLY valid JSON with this exact structure:
{{
"offboarding_id": "OB-YYYYMMDD-XXXX",
"risk_level": "LOW|MEDIUM|HIGH",
"systems_revoked": {json.dumps(systems_list)},
"files_transferred": <integer estimate of files>,
"audit_timeline": [
{{"time": "T+0min", "action": "Offboarding initiated", "status": "completed"}},
{{"time": "T+1min", "action": "Google Workspace suspended", "status": "completed"}}
],
"compliance_flags": ["flag 1 if any"],
"audit_summary": "2-3 sentence audit summary",
"it_email_subject": "...",
"it_email_body": "...",
"legal_email_subject": "...",
"legal_email_body": "...",
"manager_handoff_notes": "..."
}}
Make the timeline realistic (6-10 steps). Set risk_level based on role seniority and department.
Finance/Legal/C-suite = HIGH. Senior IC = MEDIUM. Junior = LOW.
For Finance add SOX flags. For Sales add CRM handoff flags. For VP+ add elevated security flags."""
try:
from openai import OpenAI
client = OpenAI(api_key=api_key.strip())
response = client.chat.completions.create(
model="gpt-4o-mini",
messages=[{"role": "user", "content": prompt}],
temperature=0.3,
max_tokens=1800,
response_format={"type": "json_object"},
)
raw = response.choices[0].message.content
data = json.loads(raw)
except Exception as exc:
return _error_html("API Error", f"<code>{str(exc)}</code>")
return _render_report(data, name, role, department, manager_name, last_day_str)
def _error_html(title: str, body: str) -> str:
return f"""
<div style="background:rgba(127,29,29,0.45);border:1px solid rgba(239,68,68,0.5);
border-radius:12px;padding:20px 24px;color:#fca5a5;">
<div style="font-weight:700;font-size:1.1rem;margin-bottom:8px;">❌ {title}</div>
<div style="color:#fecaca;font-size:0.9rem;">{body}</div>
</div>
"""
def _render_report(data: dict, name: str, role: str, dept: str, manager: str, last_day: str) -> str:
risk = data.get("risk_level", "MEDIUM")
risk_color, risk_bg = RISK_COLORS.get(risk, ("#e2e8f0", "rgba(50,50,80,0.4)"))
oid = data.get("offboarding_id", "OB-UNKNOWN")
files = data.get("files_transferred", 0)
systems = data.get("systems_revoked", [])
timeline = data.get("audit_timeline", [])
flags = data.get("compliance_flags", [])
summary = data.get("audit_summary", "")
it_subj = data.get("it_email_subject", "")
it_body = data.get("it_email_body", "")
legal_subj = data.get("legal_email_subject", "")
legal_body = data.get("legal_email_body", "")
handoff = data.get("manager_handoff_notes", "")
chips = "".join(_system_chip(s) for s in systems)
tl_rows = ""
for item in timeline:
tl_rows += f"""
<tr>
<td style="color:#7c3aed;font-size:0.8rem;padding:5px 12px 5px 0;
white-space:nowrap;font-family:monospace;">{item.get('time','')}</td>
<td style="color:#e2e8f0;font-size:0.85rem;padding:5px 0;">
βœ… {item.get('action','')}
</td>
</tr>
"""
flags_html = ""
if flags:
items = "".join(
f'<li style="color:#fde68a;font-size:0.85rem;margin:4px 0;">⚠️ {f}</li>'
for f in flags
)
flags_html = f"""
<div style="background:rgba(120,53,15,0.35);border:1px solid rgba(234,179,8,0.35);
border-radius:12px;padding:14px 18px;margin-bottom:14px;">
<div style="font-weight:700;color:#fcd34d;margin-bottom:8px;">⚠️ Compliance Flags</div>
<ul style="margin:0;padding-left:16px;">{items}</ul>
</div>
"""
else:
flags_html = """
<div style="background:rgba(21,128,61,0.2);border:1px solid rgba(134,239,172,0.3);
border-radius:12px;padding:12px 18px;margin-bottom:14px;">
<span style="color:#86efac;">βœ… No compliance flags raised</span>
</div>
"""
def email_block(label, to, subject, body_text, accent):
body_escaped = body_text.replace("\n", "<br>")
return f"""
<div style="background:rgba(15,23,42,0.55);border:1px solid {accent};
border-radius:12px;padding:16px 20px;margin-bottom:12px;">
<div style="font-weight:700;color:#e2e8f0;margin-bottom:6px;">πŸ“§ {label}</div>
<div style="color:#94a3b8;font-size:0.8rem;margin-bottom:4px;">
<b style="color:#a5b4fc;">To:</b> {to}
</div>
<div style="color:#94a3b8;font-size:0.8rem;margin-bottom:10px;">
<b style="color:#a5b4fc;">Subject:</b> {subject}
</div>
<div style="background:rgba(0,0,0,0.3);border-radius:8px;padding:12px;
color:#cbd5e1;font-size:0.82rem;line-height:1.6;
font-family:monospace;">{body_escaped}</div>
</div>
"""
return f"""
<div style="background:rgba(30,27,75,0.55);border:1px solid rgba(99,102,241,0.35);
border-radius:14px;padding:20px 24px;margin-bottom:14px;">
<div style="display:flex;justify-content:space-between;align-items:flex-start;flex-wrap:wrap;gap:8px;">
<div>
<div style="font-size:1.3rem;font-weight:800;color:#e2e8f0;">πŸ”’ {name}</div>
<div style="color:#a5b4fc;font-size:0.88rem;margin-top:4px;">
{role} Β· {dept} Β· Last day: {last_day} Β· Manager: {manager}
</div>
<div style="color:#94a3b8;font-size:0.8rem;margin-top:2px;">
ID: <code style="color:#c7d2fe;">{oid}</code>
</div>
</div>
<div style="display:flex;flex-direction:column;align-items:flex-end;gap:6px;">
{_badge('OFFBOARDING COMPLETE βœ…', 'rgba(21,128,61,0.45)', '#86efac')}
{_badge('Risk: ' + risk, risk_bg, risk_color)}
</div>
</div>
</div>
<div style="background:rgba(15,23,42,0.55);border:1px solid rgba(99,102,241,0.25);
border-radius:12px;padding:16px 20px;margin-bottom:14px;">
<div style="font-weight:700;color:#c7d2fe;margin-bottom:10px;">πŸ›‘οΈ Systems Revoked</div>
<div>{chips}</div>
<div style="color:#94a3b8;font-size:0.82rem;margin-top:10px;">
πŸ“ {files:,} Google Drive files transferred to {manager}
</div>
</div>
<div style="background:rgba(15,23,42,0.55);border:1px solid rgba(99,102,241,0.25);
border-radius:12px;padding:16px 20px;margin-bottom:14px;">
<div style="font-weight:700;color:#c7d2fe;margin-bottom:10px;">⏱️ Action Timeline</div>
<table style="border-collapse:collapse;width:100%;">{tl_rows}</table>
</div>
{flags_html}
<div style="background:rgba(15,23,42,0.55);border:1px solid rgba(99,102,241,0.25);
border-radius:12px;padding:16px 20px;margin-bottom:14px;">
<div style="font-weight:700;color:#c7d2fe;margin-bottom:8px;">πŸ“‹ Audit Summary</div>
<p style="color:#cbd5e1;font-size:0.88rem;line-height:1.6;margin:0;">{summary}</p>
</div>
<div style="background:rgba(15,23,42,0.55);border:1px solid rgba(99,102,241,0.25);
border-radius:12px;padding:16px 20px;margin-bottom:14px;">
<div style="font-weight:700;color:#c7d2fe;margin-bottom:8px;">πŸ“ Manager Handoff Notes</div>
<p style="color:#cbd5e1;font-size:0.88rem;line-height:1.6;margin:0;">{handoff}</p>
</div>
<div style="background:rgba(30,27,75,0.35);border:1px solid rgba(99,102,241,0.2);
border-radius:12px;padding:14px 18px;margin-bottom:14px;">
<div style="font-weight:700;color:#c7d2fe;margin-bottom:12px;">πŸ“¨ Compliance Email Previews</div>
{email_block('Email to IT Security', 'it-security@company.com', it_subj, it_body, 'rgba(99,102,241,0.4)')}
{email_block('Email to Legal / Compliance', 'legal@company.com', legal_subj, legal_body, 'rgba(239,68,68,0.4)')}
</div>
"""
# ---------------------------------------------------------------------------
# How It Works β€” static HTML
# ---------------------------------------------------------------------------
HOW_IT_WORKS_HTML = """
<div style="color:#e2e8f0;max-width:860px;margin:0 auto;padding:8px 0;">
<div style="background:rgba(30,27,75,0.55);border:1px solid rgba(99,102,241,0.35);
border-radius:14px;padding:24px 28px;margin-bottom:20px;">
<h2 style="color:#c7d2fe;margin:0 0 12px;font-size:1.25rem;">πŸ”§ n8n Workflow Overview</h2>
<p style="color:#cbd5e1;line-height:1.7;margin:0;">
This agent is powered by an <strong style="color:#a5b4fc;">n8n automation workflow</strong>
that chains together API calls to every system in your tech stack. HR triggers the workflow
via a webhook or form submission β€” everything else is hands-free.
</p>
</div>
<div style="background:rgba(15,23,42,0.55);border:1px solid rgba(99,102,241,0.25);
border-radius:12px;padding:20px 24px;margin-bottom:16px;">
<h3 style="color:#c7d2fe;margin:0 0 16px;font-size:1.05rem;">πŸ“ Workflow Steps</h3>
<div style="display:grid;gap:10px;">
<div style="background:rgba(99,102,241,0.15);border-radius:10px;padding:12px 16px;
border-left:3px solid #6366f1;">
<div style="color:#a5b4fc;font-weight:700;font-size:0.9rem;">Step 1 β€” HR Trigger</div>
<div style="color:#94a3b8;font-size:0.85rem;margin-top:4px;">
Webhook receives employee data from HR system (Workday, BambooHR, etc.) or manual form.
Validates required fields and routes to risk assessment.
</div>
</div>
<div style="background:rgba(99,102,241,0.15);border-radius:10px;padding:12px 16px;
border-left:3px solid #6366f1;">
<div style="color:#a5b4fc;font-weight:700;font-size:0.9rem;">Step 2 β€” Risk Assessment</div>
<div style="color:#94a3b8;font-size:0.85rem;margin-top:4px;">
AI node evaluates role, department, and access level to assign LOW / MEDIUM / HIGH risk.
HIGH risk triggers immediate revocation; others follow standard schedule.
</div>
</div>
<div style="background:rgba(99,102,241,0.15);border-radius:10px;padding:12px 16px;
border-left:3px solid #6366f1;">
<div style="color:#a5b4fc;font-weight:700;font-size:0.9rem;">Step 3 β€” Google Workspace</div>
<div style="color:#94a3b8;font-size:0.85rem;margin-top:4px;">
Calls Google Admin SDK: suspends account, revokes OAuth tokens, strips admin roles.
Parallel sub-flow initiates Drive file transfer to manager.
</div>
</div>
<div style="background:rgba(99,102,241,0.15);border-radius:10px;padding:12px 16px;
border-left:3px solid #6366f1;">
<div style="color:#a5b4fc;font-weight:700;font-size:0.9rem;">Step 4 β€” Slack</div>
<div style="color:#94a3b8;font-size:0.85rem;margin-top:4px;">
Calls Slack Admin API: deactivates user, removes from all channels and user groups,
revokes active sessions and OAuth tokens.
</div>
</div>
<div style="background:rgba(99,102,241,0.15);border-radius:10px;padding:12px 16px;
border-left:3px solid #6366f1;">
<div style="color:#a5b4fc;font-weight:700;font-size:0.9rem;">Step 5 β€” GitHub</div>
<div style="color:#94a3b8;font-size:0.85rem;margin-top:4px;">
Calls GitHub REST API: removes org membership, removes from all teams,
revokes SSH keys and personal access tokens.
</div>
</div>
<div style="background:rgba(99,102,241,0.15);border-radius:10px;padding:12px 16px;
border-left:3px solid #6366f1;">
<div style="color:#a5b4fc;font-weight:700;font-size:0.9rem;">Step 6 β€” Additional Systems</div>
<div style="color:#94a3b8;font-size:0.85rem;margin-top:4px;">
Conditional branches handle Salesforce (CRM handoff), Jira (ticket reassignment),
AWS (IAM deactivation), Figma, and Notion based on selected systems.
</div>
</div>
<div style="background:rgba(99,102,241,0.15);border-radius:10px;padding:12px 16px;
border-left:3px solid #6366f1;">
<div style="color:#a5b4fc;font-weight:700;font-size:0.9rem;">Step 7 β€” Audit Report</div>
<div style="color:#94a3b8;font-size:0.85rem;margin-top:4px;">
Aggregates all action results, timestamps, and compliance flags into a structured
audit report. Logs to immutable compliance database with unique Offboarding ID.
</div>
</div>
<div style="background:rgba(99,102,241,0.15);border-radius:10px;padding:12px 16px;
border-left:3px solid #6366f1;">
<div style="color:#a5b4fc;font-weight:700;font-size:0.9rem;">Step 8 β€” Compliance Notification</div>
<div style="color:#94a3b8;font-size:0.85rem;margin-top:4px;">
Sends formatted confirmation emails to IT Security and Legal/Compliance teams.
Optionally triggers Slack notification to manager with handoff notes.
</div>
</div>
</div>
</div>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:14px;margin-bottom:16px;">
<div style="background:rgba(15,23,42,0.55);border:1px solid rgba(99,102,241,0.25);
border-radius:12px;padding:18px 20px;">
<h3 style="color:#c7d2fe;margin:0 0 12px;font-size:1rem;">πŸ” Credentials Required</h3>
<ul style="color:#94a3b8;font-size:0.85rem;line-height:1.8;margin:0;padding-left:16px;">
<li>Google Workspace Admin SDK</li>
<li>Google Drive API (service account)</li>
<li>Slack Admin API token</li>
<li>GitHub Personal Access Token (org:admin)</li>
<li>Salesforce Connected App (optional)</li>
<li>AWS IAM credentials (optional)</li>
<li>SMTP credentials for email</li>
</ul>
</div>
<div style="background:rgba(15,23,42,0.55);border:1px solid rgba(99,102,241,0.25);
border-radius:12px;padding:18px 20px;">
<h3 style="color:#c7d2fe;margin:0 0 12px;font-size:1rem;">⚑ Performance</h3>
<div style="display:grid;gap:8px;">
<div style="background:rgba(99,102,241,0.15);border-radius:8px;padding:8px 12px;">
<div style="color:#a5b4fc;font-size:0.8rem;">Average completion time</div>
<div style="color:#e2e8f0;font-weight:700;">Under 8 minutes</div>
</div>
<div style="background:rgba(99,102,241,0.15);border-radius:8px;padding:8px 12px;">
<div style="color:#a5b4fc;font-size:0.8rem;">Manual steps required</div>
<div style="color:#86efac;font-weight:700;">Zero</div>
</div>
<div style="background:rgba(99,102,241,0.15);border-radius:8px;padding:8px 12px;">
<div style="color:#a5b4fc;font-size:0.8rem;">Audit trail</div>
<div style="color:#e2e8f0;font-weight:700;">100% immutable</div>
</div>
</div>
</div>
</div>
<div style="background:rgba(15,23,42,0.55);border:1px solid rgba(99,102,241,0.25);
border-radius:12px;padding:18px 20px;">
<h3 style="color:#c7d2fe;margin:0 0 12px;font-size:1rem;">πŸ—οΈ Tech Stack</h3>
<div style="display:flex;flex-wrap:wrap;gap:8px;">
<span style="background:rgba(239,68,68,0.2);color:#fca5a5;border:1px solid rgba(239,68,68,0.3);
padding:4px 12px;border-radius:8px;font-size:0.82rem;">n8n Automation</span>
<span style="background:rgba(234,179,8,0.15);color:#fde68a;border:1px solid rgba(234,179,8,0.3);
padding:4px 12px;border-radius:8px;font-size:0.82rem;">GPT-4o-mini</span>
<span style="background:rgba(99,102,241,0.2);color:#c7d2fe;border:1px solid rgba(99,102,241,0.3);
padding:4px 12px;border-radius:8px;font-size:0.82rem;">Google Workspace API</span>
<span style="background:rgba(99,102,241,0.2);color:#c7d2fe;border:1px solid rgba(99,102,241,0.3);
padding:4px 12px;border-radius:8px;font-size:0.82rem;">Slack Admin API</span>
<span style="background:rgba(99,102,241,0.2);color:#c7d2fe;border:1px solid rgba(99,102,241,0.3);
padding:4px 12px;border-radius:8px;font-size:0.82rem;">GitHub REST API</span>
<span style="background:rgba(21,128,61,0.2);color:#86efac;border:1px solid rgba(21,128,61,0.3);
padding:4px 12px;border-radius:8px;font-size:0.82rem;">Gradio 5.9.1</span>
</div>
</div>
</div>
"""
# ---------------------------------------------------------------------------
# Build the Gradio app
# ---------------------------------------------------------------------------
with gr.Blocks(
theme=gr.themes.Soft(primary_hue="red", secondary_hue="indigo"),
title="Employee Offboarding Security Agent",
css="""
.gradio-container { max-width: 960px !important; margin: 0 auto; }
footer { display: none !important; }
""",
) as demo:
gr.HTML("""
<div style="text-align:center;padding:28px 20px 18px;
background:rgba(30,27,75,0.5);border-radius:16px;margin-bottom:8px;">
<div style="font-size:2.4rem;margin-bottom:8px;">πŸ”’</div>
<h1 style="color:#e2e8f0;font-size:1.8rem;font-weight:800;margin:0 0 8px;">
Employee Offboarding Security Agent
</h1>
<p style="color:#a5b4fc;font-size:1rem;margin:0;">
Zero-touch offboarding Β· Access revoked in &lt;8 minutes Β· Full audit trail
</p>
<div style="display:flex;justify-content:center;gap:10px;margin-top:14px;flex-wrap:wrap;">
<span style="background:rgba(99,102,241,0.25);color:#c7d2fe;
border:1px solid rgba(99,102,241,0.4);padding:4px 14px;
border-radius:20px;font-size:0.82rem;">⚑ Powered by n8n</span>
<span style="background:rgba(239,68,68,0.2);color:#fca5a5;
border:1px solid rgba(239,68,68,0.35);padding:4px 14px;
border-radius:20px;font-size:0.82rem;">πŸ›‘οΈ Zero Manual Steps</span>
<span style="background:rgba(21,128,61,0.2);color:#86efac;
border:1px solid rgba(21,128,61,0.35);padding:4px 14px;
border-radius:20px;font-size:0.82rem;">πŸ“‹ SOX / GDPR Compliant</span>
</div>
</div>
""")
with gr.Tabs():
# ==================================================================
# TAB 1 β€” Live Demo
# ==================================================================
with gr.TabItem("πŸ”’ Live Demo"):
gr.HTML("""
<div style="background:rgba(15,23,42,0.5);border:1px solid rgba(99,102,241,0.25);
border-radius:10px;padding:12px 18px;margin-bottom:14px;">
<span style="color:#a5b4fc;font-size:0.9rem;">
✨ <strong style="color:#c7d2fe;">Pre-computed scenarios</strong> β€” no API key required.
Select an employee to view their full offboarding report.
</span>
</div>
""")
scenario_selector = gr.Radio(
choices=list(SCENARIOS.keys()),
value=list(SCENARIOS.keys())[0],
label="Select Offboarding Scenario",
interactive=True,
)
demo_output = gr.HTML(value=render_demo_card(list(SCENARIOS.keys())[0]))
scenario_selector.change(
fn=render_demo_card,
inputs=scenario_selector,
outputs=demo_output,
)
# ==================================================================
# TAB 2 β€” Run Offboarding
# ==================================================================
with gr.TabItem("πŸ‘€ Run Offboarding"):
gr.HTML("""
<div style="background:rgba(127,29,29,0.35);border:1px solid rgba(239,68,68,0.4);
border-radius:10px;padding:12px 18px;margin-bottom:16px;">
<span style="color:#fca5a5;font-size:0.9rem;">
πŸ”‘ Requires an <strong>OpenAI API key</strong> to call GPT-4o-mini.
Set the <code>OPENAI_API_KEY</code> HF Space secret or enter it below.
</span>
</div>
""")
with gr.Row():
with gr.Column():
emp_name = gr.Textbox(label="Employee Name", placeholder="e.g. Jane Smith")
emp_email = gr.Textbox(label="Employee Email", placeholder="jane@company.com")
emp_role = gr.Textbox(label="Role / Title", placeholder="e.g. Senior Engineer")
emp_dept = gr.Textbox(label="Department", placeholder="e.g. Engineering")
with gr.Column():
mgr_name = gr.Textbox(label="Manager Name", placeholder="e.g. Tom Bradley")
mgr_email = gr.Textbox(label="Manager Email", placeholder="tom@company.com")
last_day = gr.Textbox(
label="Last Working Day",
placeholder="e.g. 2024-07-31",
value=date.today().isoformat(),
)
reason = gr.Dropdown(
choices=["Resignation", "Termination", "Retirement", "Contract End"],
value="Resignation",
label="Reason for Offboarding",
)
extra_systems = gr.CheckboxGroup(
choices=["Salesforce", "Jira", "AWS", "Figma", "Notion"],
label="Additional Systems to Revoke (beyond Google/Slack/GitHub)",
value=[],
)
api_key_input = gr.Textbox(
label="OpenAI API Key (or set OPENAI_API_KEY secret)",
placeholder="sk-...",
type="password",
)
run_btn = gr.Button("πŸš€ Run Offboarding Agent", variant="primary", size="lg")
report_output = gr.HTML(
value="""
<div style="background:rgba(15,23,42,0.4);border:1px solid rgba(99,102,241,0.2);
border-radius:12px;padding:32px;text-align:center;color:#64748b;">
Fill in the employee details above and click <strong style="color:#a5b4fc;">
Run Offboarding Agent</strong> to generate a full report.
</div>
"""
)
run_btn.click(
fn=generate_offboarding_report,
inputs=[
emp_name, emp_email, emp_role, emp_dept,
mgr_name, mgr_email, last_day, reason,
extra_systems, api_key_input,
],
outputs=report_output,
)
# ==================================================================
# TAB 3 β€” How It Works
# ==================================================================
with gr.TabItem("βš™οΈ How It Works"):
gr.HTML(HOW_IT_WORKS_HTML)
gr.HTML("""
<div style="text-align:center;padding:14px;color:#475569;font-size:0.8rem;margin-top:8px;">
Employee Offboarding Security Agent Β· Built with n8n + Gradio 5.9.1 Β· MIT License
</div>
""")
if __name__ == "__main__":
demo.launch()