feat: gestion réseau DHCP/statique depuis l'interface web
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
41
scripts/apply_network.sh
Normal file
41
scripts/apply_network.sh
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# apply_network.sh — Configure le réseau via NetworkManager
|
||||||
|
# Usage: apply_network.sh dhcp
|
||||||
|
# ou: apply_network.sh static <ip/prefix> <gateway> <dns>
|
||||||
|
# Exemple: apply_network.sh static 192.168.1.100/24 192.168.1.1 8.8.8.8,1.1.1.1
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
MODE="$1"
|
||||||
|
CON="Wired connection 1"
|
||||||
|
|
||||||
|
# Trouve la connexion active si le nom par défaut a changé
|
||||||
|
if ! nmcli con show "$CON" &>/dev/null; then
|
||||||
|
CON=$(nmcli -t -f NAME,TYPE con show --active | grep ethernet | cut -d: -f1 | head -1)
|
||||||
|
fi
|
||||||
|
|
||||||
|
[[ -z "$CON" ]] && echo "Aucune connexion Ethernet trouvée." && exit 1
|
||||||
|
|
||||||
|
if [[ "$MODE" == "dhcp" ]]; then
|
||||||
|
nmcli con mod "$CON" ipv4.method auto ipv4.addresses "" ipv4.gateway "" ipv4.dns ""
|
||||||
|
nmcli con up "$CON"
|
||||||
|
echo "OK: mode DHCP appliqué."
|
||||||
|
|
||||||
|
elif [[ "$MODE" == "static" ]]; then
|
||||||
|
IP_PREFIX="$2" # ex: 192.168.1.100/24
|
||||||
|
GATEWAY="$3" # ex: 192.168.1.1
|
||||||
|
DNS="$4" # ex: 8.8.8.8,1.1.1.1
|
||||||
|
|
||||||
|
[[ -z "$IP_PREFIX" || -z "$GATEWAY" ]] && echo "IP et gateway requis." && exit 1
|
||||||
|
|
||||||
|
nmcli con mod "$CON" \
|
||||||
|
ipv4.method manual \
|
||||||
|
ipv4.addresses "$IP_PREFIX" \
|
||||||
|
ipv4.gateway "$GATEWAY" \
|
||||||
|
ipv4.dns "${DNS:-8.8.8.8,1.1.1.1}"
|
||||||
|
nmcli con up "$CON"
|
||||||
|
echo "OK: IP statique $IP_PREFIX appliquée."
|
||||||
|
|
||||||
|
else
|
||||||
|
echo "Usage: $0 dhcp | static <ip/prefix> <gateway> [dns]" && exit 1
|
||||||
|
fi
|
||||||
@@ -118,5 +118,169 @@ html_navbar('domain');
|
|||||||
<a href="/admin/index.php" class="btn btn-outline-secondary">Annuler</a>
|
<a href="/admin/index.php" class="btn btn-outline-secondary">Annuler</a>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
|
<!-- ── Réseau (hors formulaire principal) ── -->
|
||||||
|
<div class="card shadow-sm mt-4">
|
||||||
|
<div class="card-header fw-semibold d-flex justify-content-between align-items-center">
|
||||||
|
<span><i class="bi bi-hdd-network me-2"></i>Réseau</span>
|
||||||
|
<span id="net-current-ip" class="badge bg-secondary font-monospace">chargement…</span>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<div id="net-loading" class="text-muted small mb-3">
|
||||||
|
<div class="spinner-border spinner-border-sm me-2"></div>Lecture de la configuration…
|
||||||
|
</div>
|
||||||
|
<div id="net-form" style="display:none">
|
||||||
|
<!-- Mode -->
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label fw-semibold">Mode</label>
|
||||||
|
<div class="d-flex gap-3">
|
||||||
|
<div class="form-check">
|
||||||
|
<input class="form-check-input" type="radio" name="net-mode" id="net-dhcp" value="dhcp">
|
||||||
|
<label class="form-check-label" for="net-dhcp">DHCP <span class="text-muted small">(automatique)</span></label>
|
||||||
|
</div>
|
||||||
|
<div class="form-check">
|
||||||
|
<input class="form-check-input" type="radio" name="net-mode" id="net-static" value="static">
|
||||||
|
<label class="form-check-label" for="net-static">IP Statique</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Champs IP statique -->
|
||||||
|
<div id="net-static-fields" style="display:none">
|
||||||
|
<div class="row g-3 mb-3">
|
||||||
|
<div class="col-md-5">
|
||||||
|
<label class="form-label">Adresse IP</label>
|
||||||
|
<input type="text" id="net-ip" class="form-control font-monospace" placeholder="192.168.1.100">
|
||||||
|
</div>
|
||||||
|
<div class="col-md-2">
|
||||||
|
<label class="form-label">Masque</label>
|
||||||
|
<select id="net-prefix" class="form-select font-monospace">
|
||||||
|
<option value="24">/24 — 255.255.255.0</option>
|
||||||
|
<option value="25">/25 — 255.255.255.128</option>
|
||||||
|
<option value="16">/16 — 255.255.0.0</option>
|
||||||
|
<option value="8">/8 — 255.0.0.0</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-5">
|
||||||
|
<label class="form-label">Passerelle</label>
|
||||||
|
<input type="text" id="net-gw" class="form-control font-monospace" placeholder="192.168.1.1">
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<label class="form-label">Serveurs DNS <span class="text-muted small">(séparés par virgule)</span></label>
|
||||||
|
<input type="text" id="net-dns" class="form-control font-monospace" placeholder="8.8.8.8,1.1.1.1">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="net-alert" class="alert py-2 mb-3" style="display:none"></div>
|
||||||
|
|
||||||
|
<div class="d-flex gap-2 align-items-center">
|
||||||
|
<button class="btn btn-warning" onclick="applyNetwork()">
|
||||||
|
<i class="bi bi-hdd-network me-1"></i>Appliquer la configuration réseau
|
||||||
|
</button>
|
||||||
|
<span id="net-spinner" class="spinner-border spinner-border-sm text-warning" style="display:none"></span>
|
||||||
|
<span class="text-muted small" id="net-warn" style="display:none">
|
||||||
|
<i class="bi bi-exclamation-triangle me-1 text-warning"></i>
|
||||||
|
Si vous changez l'IP, reconnectez-vous sur la nouvelle adresse.
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
loadNetworkConfig();
|
||||||
|
|
||||||
|
document.querySelectorAll('input[name="net-mode"]').forEach(function(r) {
|
||||||
|
r.addEventListener('change', function() {
|
||||||
|
document.getElementById('net-static-fields').style.display = r.value === 'static' ? '' : 'none';
|
||||||
|
document.getElementById('net-warn').style.display = r.value === 'static' ? '' : 'none';
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
function loadNetworkConfig() {
|
||||||
|
fetch('/api/network.php')
|
||||||
|
.then(r => r.json())
|
||||||
|
.then(function(d) {
|
||||||
|
document.getElementById('net-loading').style.display = 'none';
|
||||||
|
document.getElementById('net-form').style.display = '';
|
||||||
|
|
||||||
|
// IP actuelle
|
||||||
|
const ip = d.current_ip || d.static_ip || '—';
|
||||||
|
document.getElementById('net-current-ip').textContent = ip;
|
||||||
|
|
||||||
|
// Mode
|
||||||
|
const mode = d.method || 'dhcp';
|
||||||
|
document.querySelector('input[name="net-mode"][value="' + mode + '"]').checked = true;
|
||||||
|
|
||||||
|
// Champs statique
|
||||||
|
if (mode === 'static') {
|
||||||
|
document.getElementById('net-static-fields').style.display = '';
|
||||||
|
document.getElementById('net-warn').style.display = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pré-remplissage IP statique
|
||||||
|
const sipRaw = d.static_ip || d.current_ip || '';
|
||||||
|
const sipParts = sipRaw.split('/');
|
||||||
|
document.getElementById('net-ip').value = sipParts[0] || '';
|
||||||
|
document.getElementById('net-prefix').value = sipParts[1] || '24';
|
||||||
|
document.getElementById('net-gw').value = d.static_gw || d.current_gw || '';
|
||||||
|
document.getElementById('net-dns').value = (d.static_dns || d.current_dns || '8.8.8.8,1.1.1.1').replace(/\s/g,'');
|
||||||
|
})
|
||||||
|
.catch(function() {
|
||||||
|
document.getElementById('net-loading').innerHTML = '<span class="text-danger"><i class="bi bi-exclamation-triangle me-1"></i>Impossible de lire la configuration réseau.</span>';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyNetwork() {
|
||||||
|
const mode = document.querySelector('input[name="net-mode"]:checked')?.value || 'dhcp';
|
||||||
|
const body = { mode };
|
||||||
|
|
||||||
|
if (mode === 'static') {
|
||||||
|
body.ip = document.getElementById('net-ip').value.trim();
|
||||||
|
body.prefix = document.getElementById('net-prefix').value;
|
||||||
|
body.gateway = document.getElementById('net-gw').value.trim();
|
||||||
|
body.dns = document.getElementById('net-dns').value.trim();
|
||||||
|
if (!body.ip || !body.gateway) {
|
||||||
|
showNetAlert('danger', 'Veuillez remplir l\'adresse IP et la passerelle.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
document.getElementById('net-spinner').style.display = '';
|
||||||
|
document.getElementById('net-alert').style.display = 'none';
|
||||||
|
|
||||||
|
fetch('/api/network.php', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {'Content-Type': 'application/json'},
|
||||||
|
body: JSON.stringify(body)
|
||||||
|
})
|
||||||
|
.then(r => r.json())
|
||||||
|
.then(function(d) {
|
||||||
|
document.getElementById('net-spinner').style.display = 'none';
|
||||||
|
if (d.success) {
|
||||||
|
let msg = d.msg || 'Configuration appliquée.';
|
||||||
|
if (d.new_ip) msg += ' — Reconnectez-vous sur <strong>' + d.new_ip + '</strong>';
|
||||||
|
showNetAlert('success', msg);
|
||||||
|
setTimeout(loadNetworkConfig, 2000);
|
||||||
|
} else {
|
||||||
|
showNetAlert('danger', d.error || 'Erreur inconnue.');
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(function() {
|
||||||
|
document.getElementById('net-spinner').style.display = 'none';
|
||||||
|
showNetAlert('warning', 'Pas de réponse — l\'IP a peut-être changé. Reconnectez-vous.');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function showNetAlert(type, msg) {
|
||||||
|
const el = document.getElementById('net-alert');
|
||||||
|
el.className = 'alert alert-' + type + ' py-2 mb-3';
|
||||||
|
el.innerHTML = msg;
|
||||||
|
el.style.display = '';
|
||||||
|
}
|
||||||
|
</script>
|
||||||
<?php html_foot(); ?>
|
<?php html_foot(); ?>
|
||||||
|
|||||||
91
web/api/network.php
Normal file
91
web/api/network.php
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
<?php
|
||||||
|
require_once __DIR__ . '/../includes/db.php';
|
||||||
|
require_once __DIR__ . '/../includes/auth.php';
|
||||||
|
|
||||||
|
admin_check();
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
|
||||||
|
// Lit la config réseau actuelle via nmcli
|
||||||
|
function get_network_info(): array {
|
||||||
|
// Connexion active
|
||||||
|
$conName = trim(shell_exec("nmcli -t -f NAME,TYPE con show --active 2>/dev/null | grep ethernet | cut -d: -f1 | head -1") ?? '');
|
||||||
|
if (!$conName) $conName = 'Wired connection 1';
|
||||||
|
|
||||||
|
// Méthode (dhcp ou static)
|
||||||
|
$method = trim(shell_exec("nmcli -g ipv4.method con show " . escapeshellarg($conName) . " 2>/dev/null") ?? 'auto');
|
||||||
|
|
||||||
|
// IP actuelle
|
||||||
|
$ip4 = trim(shell_exec("nmcli -g IP4.ADDRESS dev show eth0 2>/dev/null | head -1") ?? '');
|
||||||
|
|
||||||
|
// Gateway actuelle
|
||||||
|
$gw = trim(shell_exec("nmcli -g IP4.GATEWAY dev show eth0 2>/dev/null | head -1") ?? '');
|
||||||
|
|
||||||
|
// DNS
|
||||||
|
$dns = trim(shell_exec("nmcli -g IP4.DNS dev show eth0 2>/dev/null | head -3 | tr '\n' ','") ?? '');
|
||||||
|
$dns = rtrim($dns, ',');
|
||||||
|
|
||||||
|
// Config statique configurée (pas forcément active)
|
||||||
|
$staticIp = trim(shell_exec("nmcli -g ipv4.addresses con show " . escapeshellarg($conName) . " 2>/dev/null") ?? '');
|
||||||
|
$staticGw = trim(shell_exec("nmcli -g ipv4.gateway con show " . escapeshellarg($conName) . " 2>/dev/null") ?? '');
|
||||||
|
$staticDns = trim(shell_exec("nmcli -g ipv4.dns con show " . escapeshellarg($conName) . " 2>/dev/null") ?? '');
|
||||||
|
|
||||||
|
return [
|
||||||
|
'connection' => $conName,
|
||||||
|
'method' => ($method === 'auto' || $method === 'dhcp') ? 'dhcp' : 'static',
|
||||||
|
'current_ip' => $ip4,
|
||||||
|
'current_gw' => $gw,
|
||||||
|
'current_dns'=> $dns,
|
||||||
|
'static_ip' => $staticIp,
|
||||||
|
'static_gw' => $staticGw,
|
||||||
|
'static_dns' => $staticDns,
|
||||||
|
'hostname' => gethostname(),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'GET') {
|
||||||
|
echo json_encode(get_network_info());
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||||
|
$data = json_decode(file_get_contents('php://input'), true) ?? [];
|
||||||
|
$mode = $data['mode'] ?? 'dhcp';
|
||||||
|
|
||||||
|
if ($mode === 'dhcp') {
|
||||||
|
$out = shell_exec('sudo /opt/copymail/scripts/apply_network.sh dhcp 2>&1');
|
||||||
|
echo json_encode(['success' => true, 'msg' => trim($out)]);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($mode === 'static') {
|
||||||
|
$ip = trim($data['ip'] ?? '');
|
||||||
|
$prefix = trim($data['prefix'] ?? '24');
|
||||||
|
$gateway = trim($data['gateway'] ?? '');
|
||||||
|
$dns = trim($data['dns'] ?? '8.8.8.8,1.1.1.1');
|
||||||
|
|
||||||
|
// Validation basique
|
||||||
|
if (!filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
|
||||||
|
http_response_code(400);
|
||||||
|
echo json_encode(['error' => 'Adresse IP invalide.']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
if (!filter_var($gateway, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
|
||||||
|
http_response_code(400);
|
||||||
|
echo json_encode(['error' => 'Passerelle invalide.']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
$prefix = max(1, min(30, (int)$prefix));
|
||||||
|
|
||||||
|
$out = shell_exec(sprintf(
|
||||||
|
'sudo /opt/copymail/scripts/apply_network.sh static %s %s %s 2>&1',
|
||||||
|
escapeshellarg("{$ip}/{$prefix}"),
|
||||||
|
escapeshellarg($gateway),
|
||||||
|
escapeshellarg($dns)
|
||||||
|
));
|
||||||
|
echo json_encode(['success' => true, 'msg' => trim($out), 'new_ip' => $ip]);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
http_response_code(400);
|
||||||
|
echo json_encode(['error' => 'Mode invalide.']);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user