feat: intégration Vision — licence copymail, statut dashboard, clé dans config
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -6,7 +6,7 @@ require_once __DIR__ . '/../includes/layout.php';
|
|||||||
admin_check();
|
admin_check();
|
||||||
|
|
||||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||||
$fields = ['domain', 'hostname', 'smtp_port', 'submission_port', 'imap_port', 'imaps_port', 'smb_workgroup'];
|
$fields = ['domain', 'hostname', 'smtp_port', 'submission_port', 'imap_port', 'imaps_port', 'smb_workgroup', 'vision_license_key'];
|
||||||
foreach ($fields as $f) {
|
foreach ($fields as $f) {
|
||||||
$val = trim($_POST[$f] ?? '');
|
$val = trim($_POST[$f] ?? '');
|
||||||
if ($val !== '') cfg_set($f, $val);
|
if ($val !== '') cfg_set($f, $val);
|
||||||
@@ -85,6 +85,32 @@ html_navbar('domain');
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="card shadow-sm mb-4">
|
||||||
|
<div class="card-header fw-semibold"><i class="bi bi-shield-check me-2"></i>Licence Vision</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<?php
|
||||||
|
require_once __DIR__ . '/../includes/vision.php';
|
||||||
|
$lic = vision_check();
|
||||||
|
$licClass = $lic['valid'] ? 'success' : 'danger';
|
||||||
|
$licIcon = $lic['valid'] ? 'check-circle-fill' : 'x-circle-fill';
|
||||||
|
$licMsg = $lic['valid']
|
||||||
|
? ('Valide — ' . htmlspecialchars($lic['client'] ?? '') . (!empty($lic['expires']) ? ' — expire le ' . date('d/m/Y', strtotime($lic['expires'])) : ''))
|
||||||
|
: htmlspecialchars($lic['msg'] ?? 'Non valide');
|
||||||
|
if (!empty($lic['degraded'])) $licClass = 'warning';
|
||||||
|
?>
|
||||||
|
<div class="alert alert-<?= $licClass ?> py-2 mb-3">
|
||||||
|
<i class="bi bi-<?= $licIcon ?> me-2"></i><?= $licMsg ?>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-8">
|
||||||
|
<label class="form-label">Clé de licence</label>
|
||||||
|
<input type="text" name="vision_license_key" class="form-control font-monospace"
|
||||||
|
value="<?= htmlspecialchars(cfg('vision_license_key', '')) ?>"
|
||||||
|
placeholder="COPYDEV-XXXXXXXX-XXXXXXXX-XXXXXXXX">
|
||||||
|
<div class="form-text">Générée depuis <strong>Vision</strong> → onglet Licences → produit CopyMail.</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="d-flex gap-2">
|
<div class="d-flex gap-2">
|
||||||
<button type="submit" class="btn btn-primary">
|
<button type="submit" class="btn btn-primary">
|
||||||
<i class="bi bi-floppy-fill me-1"></i>Sauvegarder & Appliquer
|
<i class="bi bi-floppy-fill me-1"></i>Sauvegarder & Appliquer
|
||||||
|
|||||||
@@ -2,8 +2,10 @@
|
|||||||
require_once __DIR__ . '/../includes/db.php';
|
require_once __DIR__ . '/../includes/db.php';
|
||||||
require_once __DIR__ . '/../includes/auth.php';
|
require_once __DIR__ . '/../includes/auth.php';
|
||||||
require_once __DIR__ . '/../includes/layout.php';
|
require_once __DIR__ . '/../includes/layout.php';
|
||||||
|
require_once __DIR__ . '/../includes/vision.php';
|
||||||
|
|
||||||
admin_check();
|
admin_check();
|
||||||
|
$licence = vision_check();
|
||||||
|
|
||||||
$services = [
|
$services = [
|
||||||
'postfix' => ['Postfix (SMTP)', 'bi-envelope-at'],
|
'postfix' => ['Postfix (SMTP)', 'bi-envelope-at'],
|
||||||
@@ -38,6 +40,23 @@ html_navbar('status');
|
|||||||
<div class="container-fluid">
|
<div class="container-fluid">
|
||||||
<h5 class="mb-4"><i class="bi bi-activity me-2"></i>Tableau de bord</h5>
|
<h5 class="mb-4"><i class="bi bi-activity me-2"></i>Tableau de bord</h5>
|
||||||
|
|
||||||
|
<!-- Statut licence -->
|
||||||
|
<?php
|
||||||
|
$lClass = $licence['valid'] ? 'success' : (isset($licence['unconfigured']) ? 'secondary' : 'danger');
|
||||||
|
if (!empty($licence['degraded'])) $lClass = 'warning';
|
||||||
|
$lIcon = $licence['valid'] ? 'shield-check' : 'shield-x';
|
||||||
|
$lText = $licence['valid']
|
||||||
|
? 'Licence valide — ' . htmlspecialchars($licence['client'] ?? 'CopyMail')
|
||||||
|
. (!empty($licence['expires']) ? ' — expire le ' . date('d/m/Y', strtotime($licence['expires'])) : '')
|
||||||
|
: htmlspecialchars($licence['msg'] ?? 'Licence non valide');
|
||||||
|
?>
|
||||||
|
<div class="alert alert-<?= $lClass ?> d-flex align-items-center justify-content-between py-2 mb-4">
|
||||||
|
<span><i class="bi bi-<?= $lIcon ?> me-2"></i><?= $lText ?></span>
|
||||||
|
<?php if (!$licence['valid'] || isset($licence['unconfigured'])): ?>
|
||||||
|
<a href="/admin/domain.php" class="btn btn-sm btn-outline-<?= $lClass ?>">Configurer</a>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Stats -->
|
<!-- Stats -->
|
||||||
<div class="row g-3 mb-4">
|
<div class="row g-3 mb-4">
|
||||||
<div class="col-sm-4">
|
<div class="col-sm-4">
|
||||||
|
|||||||
@@ -1,76 +1,65 @@
|
|||||||
<?php
|
<?php
|
||||||
/**
|
/**
|
||||||
* vision.php — Intégration Vision (licence + config)
|
* vision.php — Intégration Vision (licence)
|
||||||
* CopyMail s'enregistre auprès de Vision et vérifie sa licence.
|
* API : GET https://vision.copydev.fr/api/license.php?key=<key>&machine_id=<id>&hostname=<host>
|
||||||
* À compléter avec l'endpoint et le format exact de l'API Vision.
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
define('VISION_API_URL', 'https://vision.copydev.fr/api');
|
define('VISION_LICENSE_URL', 'https://vision.copydev.fr/api/license.php');
|
||||||
define('VISION_APP_ID', 'copymail');
|
define('VISION_PRODUCT', 'copymail');
|
||||||
|
|
||||||
function vision_check(): array {
|
function vision_machine_id(): string {
|
||||||
$keyprog = cfg('vision_keyprog', '');
|
$id = cfg('vision_machine_id', '');
|
||||||
if (!$keyprog) {
|
if (!$id) {
|
||||||
return ['valid' => false, 'msg' => 'Clé Vision non configurée.'];
|
$id = strtolower('copymail-' . bin2hex(random_bytes(6)));
|
||||||
|
cfg_set('vision_machine_id', $id);
|
||||||
|
}
|
||||||
|
return $id;
|
||||||
}
|
}
|
||||||
|
|
||||||
$payload = [
|
function vision_check(): array {
|
||||||
'app' => VISION_APP_ID,
|
$key = cfg('vision_license_key', '');
|
||||||
'keyprog' => $keyprog,
|
if (!$key) {
|
||||||
'version' => cfg('app_version', '1.0'),
|
return ['valid' => false, 'msg' => 'Licence non configurée.', 'unconfigured' => true];
|
||||||
|
}
|
||||||
|
|
||||||
|
$url = VISION_LICENSE_URL . '?' . http_build_query([
|
||||||
|
'key' => $key,
|
||||||
|
'machine_id' => vision_machine_id(),
|
||||||
'hostname' => cfg('hostname', gethostname()),
|
'hostname' => cfg('hostname', gethostname()),
|
||||||
'ip' => $_SERVER['SERVER_ADDR'] ?? '',
|
]);
|
||||||
];
|
|
||||||
|
|
||||||
$ctx = stream_context_create(['http' => [
|
$ctx = stream_context_create(['http' => [
|
||||||
'method' => 'POST',
|
'method' => 'GET',
|
||||||
'header' => "Content-Type: application/json\r\n",
|
|
||||||
'content' => json_encode($payload),
|
|
||||||
'timeout' => 5,
|
'timeout' => 5,
|
||||||
'ignore_errors' => true,
|
'ignore_errors' => true,
|
||||||
]]);
|
]]);
|
||||||
|
|
||||||
$resp = @file_get_contents(VISION_API_URL . '/check', false, $ctx);
|
$resp = @file_get_contents($url, false, $ctx);
|
||||||
|
|
||||||
if ($resp === false) {
|
if ($resp === false) {
|
||||||
return ['valid' => true, 'msg' => 'Vision injoignable — mode dégradé.'];
|
// Vision injoignable — on vérifie le délai offline autorisé
|
||||||
|
$lastOk = (int)cfg('vision_last_ok', '0');
|
||||||
|
$timeout = (int)cfg('vision_offline_timeout', '72');
|
||||||
|
$elapsed = (time() - $lastOk) / 3600;
|
||||||
|
if ($lastOk && $elapsed < $timeout) {
|
||||||
|
return ['valid' => true, 'msg' => "Vision injoignable (mode dégradé, {$timeout}h max)", 'degraded' => true];
|
||||||
|
}
|
||||||
|
return ['valid' => false, 'msg' => 'Vision injoignable et délai offline dépassé.'];
|
||||||
}
|
}
|
||||||
|
|
||||||
$data = json_decode($resp, true);
|
$data = json_decode($resp, true);
|
||||||
|
|
||||||
|
if (!empty($data['valid'])) {
|
||||||
|
cfg_set('vision_last_ok', (string)time());
|
||||||
|
cfg_set('vision_offline_timeout', (string)($data['offline_timeout_hours'] ?? 72));
|
||||||
return [
|
return [
|
||||||
'valid' => (bool)($data['valid'] ?? false),
|
'valid' => true,
|
||||||
'msg' => $data['message'] ?? '',
|
'client' => $data['client'] ?? '',
|
||||||
'config' => $data['config'] ?? [],
|
'product' => $data['product'] ?? '',
|
||||||
|
'expires' => $data['expires_at'] ?? null,
|
||||||
|
'devices' => $data['max_devices'] ?? 1,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
function vision_push_config(array $config): void {
|
return ['valid' => false, 'msg' => $data['message'] ?? 'Licence invalide.'];
|
||||||
// Applique la config reçue depuis Vision en BDD
|
|
||||||
foreach ($config as $key => $value) {
|
|
||||||
cfg_set($key, $value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function vision_register(): bool {
|
|
||||||
$keyprog = cfg('vision_keyprog', '');
|
|
||||||
if (!$keyprog) return false;
|
|
||||||
|
|
||||||
$payload = [
|
|
||||||
'app' => VISION_APP_ID,
|
|
||||||
'keyprog' => $keyprog,
|
|
||||||
'hostname' => cfg('hostname', gethostname()),
|
|
||||||
'domain' => cfg('domain', ''),
|
|
||||||
'version' => cfg('app_version', '1.0'),
|
|
||||||
'accounts' => (int)db()->query('SELECT COUNT(*) FROM accounts WHERE active=1')->fetchColumn(),
|
|
||||||
];
|
|
||||||
|
|
||||||
$ctx = stream_context_create(['http' => [
|
|
||||||
'method' => 'POST',
|
|
||||||
'header' => "Content-Type: application/json\r\n",
|
|
||||||
'content' => json_encode($payload),
|
|
||||||
'timeout' => 5,
|
|
||||||
'ignore_errors' => true,
|
|
||||||
]]);
|
|
||||||
|
|
||||||
$resp = @file_get_contents(VISION_API_URL . '/register', false, $ctx);
|
|
||||||
return $resp !== false;
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user