feat: add support for inbound trunk registration (Receive mode) for FXO gateways like Grandstream HT813 and analog Livebox integration
This commit is contained in:
1 parent
981c650c7a
commit
8a4d7a0504
5 files changed
+285
-37
No files matched your search
+29
-8
@@ -1086,7 +1086,15 @@ try {
|
||||
$username = trim((string)($payload['username'] ?? ''));
|
||||
$auth_username = trim((string)($payload['auth_username'] ?? ''));
|
||||
$secret = trim((string)($payload['secret'] ?? ''));
|
||||
$register = ($auth_mode === 'ip') ? 0 : (int)!empty($payload['register']);
|
||||
|
||||
$rawRegMode = strtolower(trim((string)($payload['registration_mode'] ?? '')));
|
||||
if (in_array($rawRegMode, ['send', 'receive', 'none'], true)) {
|
||||
$registration_mode = $rawRegMode;
|
||||
} else {
|
||||
$registration_mode = (!empty($payload['register']) && $auth_mode !== 'ip') ? 'send' : 'none';
|
||||
}
|
||||
$register = ($registration_mode === 'send') ? 1 : 0;
|
||||
|
||||
$server_uri = trim((string)($payload['server_uri'] ?? ''));
|
||||
$client_uri = trim((string)($payload['client_uri'] ?? ''));
|
||||
$contact_user = trim((string)($payload['contact_user'] ?? ''));
|
||||
@@ -1130,8 +1138,17 @@ try {
|
||||
$cleanDidRoutes = ConfigGenerator::normalizeInboundRoutes(json_encode($didRoutesRaw));
|
||||
$did_routes_json = json_encode($cleanDidRoutes, JSON_UNESCAPED_UNICODE);
|
||||
|
||||
if ($name === '' || $host === '') {
|
||||
throw new InvalidArgumentException("Le nom du Trunk et l'hôte SIP opérateur sont requis.");
|
||||
if ($name === '') {
|
||||
throw new InvalidArgumentException("Le nom du Trunk est requis.");
|
||||
}
|
||||
if ($registration_mode === 'receive') {
|
||||
if ($username === '' || $secret === '') {
|
||||
throw new InvalidArgumentException("En mode Réception (ex: passerelle Grandstream HT813 / Livebox), l'Identifiant (Username) et le Mot de passe SIP sont requis pour authentifier la passerelle.");
|
||||
}
|
||||
} else {
|
||||
if ($host === '') {
|
||||
throw new InvalidArgumentException("L'hôte SIP opérateur (Serveur SIP) est requis.");
|
||||
}
|
||||
}
|
||||
|
||||
$params = [
|
||||
@@ -1144,6 +1161,7 @@ try {
|
||||
':username' => $username,
|
||||
':auth_username' => $auth_username,
|
||||
':secret' => $secret,
|
||||
':registration_mode' => $registration_mode,
|
||||
':register' => $register,
|
||||
':server_uri' => $server_uri,
|
||||
':client_uri' => $client_uri,
|
||||
@@ -1192,7 +1210,8 @@ try {
|
||||
UPDATE trunks SET
|
||||
name = :name, host = :host, port = :port, outbound_proxy = :outbound_proxy,
|
||||
transport = :transport, auth_mode = :auth_mode, username = :username,
|
||||
auth_username = :auth_username, secret = :secret, register = :register,
|
||||
auth_username = :auth_username, secret = :secret,
|
||||
registration_mode = :registration_mode, register = :register,
|
||||
server_uri = :server_uri, client_uri = :client_uri, contact_user = :contact_user,
|
||||
expiration = :expiration, retry_interval = :retry_interval,
|
||||
max_retries = :max_retries, forbidden_retry_interval = :forbidden_retry_interval,
|
||||
@@ -1217,7 +1236,7 @@ try {
|
||||
$stmt = $db->prepare('
|
||||
INSERT INTO trunks (
|
||||
name, host, port, outbound_proxy, transport, auth_mode, username, auth_username, secret,
|
||||
register, server_uri, client_uri, contact_user, expiration, retry_interval,
|
||||
registration_mode, register, server_uri, client_uri, contact_user, expiration, retry_interval,
|
||||
max_retries, forbidden_retry_interval, fatal_retry_interval, line_support,
|
||||
from_user, from_domain, outbound_callerid, send_pai, send_rpid, send_diversion,
|
||||
trust_id_inbound, codecs, dtmf_mode, media_encryption, nat_symmetric, direct_media,
|
||||
@@ -1226,7 +1245,7 @@ try {
|
||||
outbound_prefix, custom_endpoint_params, custom_reg_params, custom_aor_params, moh_sound_id, enabled
|
||||
) VALUES (
|
||||
:name, :host, :port, :outbound_proxy, :transport, :auth_mode, :username, :auth_username, :secret,
|
||||
:register, :server_uri, :client_uri, :contact_user, :expiration, :retry_interval,
|
||||
:registration_mode, :register, :server_uri, :client_uri, :contact_user, :expiration, :retry_interval,
|
||||
:max_retries, :forbidden_retry_interval, :fatal_retry_interval, :line_support,
|
||||
:from_user, :from_domain, :outbound_callerid, :send_pai, :send_rpid, :send_diversion,
|
||||
:trust_id_inbound, :codecs, :dtmf_mode, :media_encryption, :nat_symmetric, :direct_media,
|
||||
@@ -1237,7 +1256,8 @@ try {
|
||||
ON CONFLICT(name) DO UPDATE SET
|
||||
host = excluded.host, port = excluded.port, outbound_proxy = excluded.outbound_proxy,
|
||||
transport = excluded.transport, auth_mode = excluded.auth_mode, username = excluded.username,
|
||||
auth_username = excluded.auth_username, secret = excluded.secret, register = excluded.register,
|
||||
auth_username = excluded.auth_username, secret = excluded.secret,
|
||||
registration_mode = excluded.registration_mode, register = excluded.register,
|
||||
server_uri = excluded.server_uri, client_uri = excluded.client_uri, contact_user = excluded.contact_user,
|
||||
expiration = excluded.expiration, retry_interval = excluded.retry_interval,
|
||||
max_retries = excluded.max_retries, forbidden_retry_interval = excluded.forbidden_retry_interval,
|
||||
@@ -1259,9 +1279,10 @@ try {
|
||||
$stmt->execute($params);
|
||||
}
|
||||
|
||||
$displayHost = $host !== '' ? "{$host}:{$port}" : "Passerelle locale / HT813";
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'message' => "Trunk SIP « {$name} » ({$host}:{$port}) sauvegardé avec tous ses paramètres PJSIP.",
|
||||
'message' => "Trunk SIP « {$name} » ({$displayHost}) sauvegardé avec tous ses paramètres PJSIP.",
|
||||
]);
|
||||
break;
|
||||
|
||||
|
||||
+110
-5
@@ -1579,14 +1579,25 @@ function renderTrunksTable() {
|
||||
tbody.innerHTML = rows.map(t => {
|
||||
const slug = `trunk-${slugifyTrunkName(t.name)}`;
|
||||
const regInfo = regs[slug];
|
||||
const regMode = t.registration_mode || (Number(t.register) === 1 ? 'send' : 'none');
|
||||
const isIpAuth = t.auth_mode === 'ip';
|
||||
const wantsReg = !isIpAuth && Number(t.register) === 1;
|
||||
|
||||
let statusBadge = '';
|
||||
if (Number(t.enabled ?? 1) === 0) {
|
||||
statusBadge = `<span class="px-2.5 py-0.5 rounded-full text-xs font-medium bg-anthracite-950 text-slate-400 border border-anthracite-700">Désactivé</span>`;
|
||||
} else if (isIpAuth || !wantsReg) {
|
||||
statusBadge = `<span class="px-2.5 py-0.5 rounded-full text-xs font-semibold bg-cyan-500/15 text-cyan-300 border border-cyan-500/30">Peering / Sans Reg.</span>`;
|
||||
} else if (regMode === 'receive') {
|
||||
const contacts = appState.metrics?.asterisk?.contacts || [];
|
||||
const isOnline = contacts.some(c =>
|
||||
(c.extension === slug || c.extension === t.username || (t.username && c.uri && c.uri.includes(t.username)))
|
||||
&& String(c.status || '').toLowerCase().includes('avail')
|
||||
);
|
||||
if (isOnline) {
|
||||
statusBadge = `<span class="inline-flex items-center gap-1.5 px-2.5 py-0.5 rounded-full text-xs font-bold bg-emerald-500/20 text-emerald-300 border border-emerald-500/30"><i class="w-1.5 h-1.5 rounded-full bg-emerald-400 animate-pulse"></i>Passerelle En ligne</span>`;
|
||||
} else {
|
||||
statusBadge = `<span class="inline-flex items-center gap-1.5 px-2.5 py-0.5 rounded-full text-xs font-semibold bg-amber-500/20 text-amber-300 border border-amber-500/30">En attente passerelle (Receive)</span>`;
|
||||
}
|
||||
} else if (isIpAuth || regMode === 'none') {
|
||||
statusBadge = `<span class="px-2.5 py-0.5 rounded-full text-xs font-semibold bg-purple-500/15 text-purple-300 border border-purple-500/30">Peering IP direct</span>`;
|
||||
} else if (regInfo && /Registered/i.test(regInfo.status)) {
|
||||
statusBadge = `<span class="inline-flex items-center gap-1.5 px-2.5 py-0.5 rounded-full text-xs font-bold bg-emerald-500/20 text-emerald-300 border border-emerald-500/30"><i class="w-1.5 h-1.5 rounded-full bg-emerald-400 animate-pulse"></i>Registered</span>`;
|
||||
} else if (regInfo) {
|
||||
@@ -1618,7 +1629,9 @@ function renderTrunksTable() {
|
||||
</td>
|
||||
<td class="py-3.5 px-4">${statusBadge}</td>
|
||||
<td class="py-3.5 px-4">
|
||||
<div class="font-mono text-xs text-indigo-300">${escapeHtml(t.host)}:${Number(t.port)}</div>
|
||||
<div class="font-mono text-xs ${t.host ? 'text-indigo-300' : 'text-emerald-300'}">
|
||||
${t.host ? escapeHtml(t.host) + ':' + Number(t.port || 5060) : 'Passerelle locale (Dynamique)'}
|
||||
</div>
|
||||
${t.outbound_proxy ? `<div class="text-[11px] font-mono text-slate-400 truncate max-w-[180px]">Proxy: ${escapeHtml(t.outbound_proxy)}</div>` : ''}
|
||||
</td>
|
||||
<td class="py-3.5 px-4">
|
||||
@@ -2085,6 +2098,57 @@ function parseFreePbxConfig() {
|
||||
}
|
||||
}
|
||||
|
||||
function onTrunkRegistrationModeChange(mode) {
|
||||
const form = document.getElementById('form-trunk');
|
||||
if (!form) return;
|
||||
|
||||
const hostInput = form.elements['host'];
|
||||
const hostLabel = document.getElementById('trunk-host-label');
|
||||
const hostHint = document.getElementById('trunk-host-hint');
|
||||
const receiveGuide = document.getElementById('trunk-receive-guide');
|
||||
const regNotice = document.getElementById('trunk-reg-outbound-notice');
|
||||
|
||||
if (mode === 'receive') {
|
||||
if (hostInput) {
|
||||
hostInput.required = false;
|
||||
hostInput.placeholder = 'Optionnel (vide = IP découverte dynamiquement au REGISTER du HT813)';
|
||||
}
|
||||
if (hostLabel) {
|
||||
hostLabel.innerHTML = 'Serveur SIP / IP Passerelle <span class="text-emerald-400 font-semibold">(Optionnel en mode Réception)</span>';
|
||||
}
|
||||
hostHint?.classList.remove('hidden');
|
||||
receiveGuide?.classList.remove('hidden');
|
||||
regNotice?.classList.remove('hidden');
|
||||
if (form.elements['register']) form.elements['register'].checked = false;
|
||||
if (form.elements['line_support']) form.elements['line_support'].checked = false;
|
||||
} else if (mode === 'none') {
|
||||
if (hostInput) {
|
||||
hostInput.required = true;
|
||||
hostInput.placeholder = 'ex: 192.168.1.250 ou sbc.direct.net';
|
||||
}
|
||||
if (hostLabel) {
|
||||
hostLabel.innerHTML = 'Serveur SIP / SBC / Passerelle * <span class="text-slate-400">(Host / IP)</span>';
|
||||
}
|
||||
hostHint?.classList.add('hidden');
|
||||
receiveGuide?.classList.add('hidden');
|
||||
regNotice?.classList.remove('hidden');
|
||||
if (form.elements['register']) form.elements['register'].checked = false;
|
||||
} else {
|
||||
// mode === 'send'
|
||||
if (hostInput) {
|
||||
hostInput.required = true;
|
||||
hostInput.placeholder = 'ex: sbc.operateur.net ou siptrunk.ovh.net';
|
||||
}
|
||||
if (hostLabel) {
|
||||
hostLabel.innerHTML = 'Serveur SIP / SBC / Passerelle * <span class="text-slate-400">(Host / IP)</span>';
|
||||
}
|
||||
hostHint?.classList.add('hidden');
|
||||
receiveGuide?.classList.add('hidden');
|
||||
regNotice?.classList.add('hidden');
|
||||
if (form.elements['register']) form.elements['register'].checked = true;
|
||||
}
|
||||
}
|
||||
|
||||
function applyTrunkArchitecture(arch) {
|
||||
const form = document.getElementById('form-trunk');
|
||||
if (!form) return;
|
||||
@@ -2095,17 +2159,31 @@ function applyTrunkArchitecture(arch) {
|
||||
port: 5060,
|
||||
transport: 'udp',
|
||||
auth_mode: 'userpass',
|
||||
registration_mode: 'send',
|
||||
register: true,
|
||||
line_support: true,
|
||||
media_encryption: 'no',
|
||||
nat_symmetric: true,
|
||||
qualify_frequency: 60
|
||||
},
|
||||
gateway_receive: {
|
||||
label: 'Passerelle FXO / Analogique (Grandstream HT813, Livebox, ATA)',
|
||||
port: 5060,
|
||||
transport: 'udp',
|
||||
auth_mode: 'userpass',
|
||||
registration_mode: 'receive',
|
||||
register: false,
|
||||
line_support: false,
|
||||
media_encryption: 'no',
|
||||
nat_symmetric: true,
|
||||
qualify_frequency: 60
|
||||
},
|
||||
ip_peering: {
|
||||
label: 'Peering IP / SBC / Passerelle (Sans REGISTER)',
|
||||
port: 5060,
|
||||
transport: 'udp',
|
||||
auth_mode: 'ip',
|
||||
registration_mode: 'none',
|
||||
register: false,
|
||||
line_support: false,
|
||||
media_encryption: 'no',
|
||||
@@ -2117,6 +2195,7 @@ function applyTrunkArchitecture(arch) {
|
||||
port: 5061,
|
||||
transport: 'tls',
|
||||
auth_mode: 'userpass',
|
||||
registration_mode: 'send',
|
||||
register: true,
|
||||
line_support: true,
|
||||
media_encryption: 'sdes',
|
||||
@@ -2134,6 +2213,13 @@ function applyTrunkArchitecture(arch) {
|
||||
form.querySelectorAll('input[name="auth_mode"]').forEach(r => { r.checked = (r.value === a.auth_mode); });
|
||||
toggleTrunkAuthFields(a.auth_mode);
|
||||
|
||||
if (a.registration_mode) {
|
||||
form.querySelectorAll('input[name="registration_mode"]').forEach(r => {
|
||||
r.checked = (r.value === a.registration_mode);
|
||||
});
|
||||
onTrunkRegistrationModeChange(a.registration_mode);
|
||||
}
|
||||
|
||||
form.elements['register'].checked = a.register;
|
||||
form.elements['line_support'].checked = a.line_support;
|
||||
form.elements['media_encryption'].value = a.media_encryption;
|
||||
@@ -2152,11 +2238,18 @@ function openTrunkModal(trunk = null) {
|
||||
|
||||
form.elements['id'].value = trunk ? trunk.id : '';
|
||||
form.elements['name'].value = trunk ? trunk.name : '';
|
||||
form.elements['host'].value = trunk ? trunk.host : '';
|
||||
form.elements['host'].value = trunk ? (trunk.host ?? '') : '';
|
||||
form.elements['port'].value = trunk ? (trunk.port ?? 5060) : 5060;
|
||||
form.elements['outbound_proxy'].value = trunk ? (trunk.outbound_proxy ?? '') : '';
|
||||
form.elements['transport'].value = trunk ? (trunk.transport ?? 'udp') : 'udp';
|
||||
|
||||
const regMode = trunk ? (trunk.registration_mode || (Number(trunk.register) === 1 ? 'send' : 'none')) : 'send';
|
||||
form.querySelectorAll('input[name="registration_mode"]').forEach(r => {
|
||||
r.checked = (r.value === regMode);
|
||||
});
|
||||
onTrunkRegistrationModeChange(regMode);
|
||||
form.elements['transport'].value = trunk ? (trunk.transport ?? 'udp') : 'udp';
|
||||
|
||||
const authMode = trunk ? (trunk.auth_mode || 'userpass') : 'userpass';
|
||||
form.querySelectorAll('input[name="auth_mode"]').forEach(r => {
|
||||
r.checked = (r.value === authMode);
|
||||
@@ -2168,6 +2261,17 @@ function openTrunkModal(trunk = null) {
|
||||
form.elements['secret'].value = trunk ? (trunk.secret ?? '') : '';
|
||||
form.elements['enabled'].checked = trunk ? Number(trunk.enabled ?? 1) === 1 : true;
|
||||
|
||||
const uInput = form.elements['username'];
|
||||
const updateGuide = () => {
|
||||
const val = uInput.value.trim() || 'Même valeur que Username';
|
||||
const gUser = document.getElementById('ht813-guide-user');
|
||||
const gAuth = document.getElementById('ht813-guide-auth');
|
||||
if (gUser) gUser.textContent = val;
|
||||
if (gAuth) gAuth.textContent = val;
|
||||
};
|
||||
uInput.oninput = updateGuide;
|
||||
updateGuide();
|
||||
|
||||
// Onglet 2 : Registration & Identité SIP
|
||||
form.elements['register'].checked = trunk ? Number(trunk.register ?? 1) === 1 : true;
|
||||
form.elements['line_support'].checked = trunk ? Number(trunk.line_support ?? 1) === 1 : true;
|
||||
@@ -3583,6 +3687,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
const fd = new FormData(form);
|
||||
const payload = Object.fromEntries(fd.entries());
|
||||
|
||||
payload.registration_mode = form.querySelector('input[name="registration_mode"]:checked')?.value || 'send';
|
||||
payload.enabled = form.elements['enabled'].checked ? 1 : 0;
|
||||
payload.register = form.elements['register'].checked ? 1 : 0;
|
||||
payload.line_support = form.elements['line_support'].checked ? 1 : 0;
|
||||
|
||||
+74
-4
@@ -3278,6 +3278,9 @@ $currentUser = Auth::getUsername();
|
||||
<button type="button" onclick="applyTrunkArchitecture('register_auth')" class="px-2.5 py-1 rounded-lg bg-anthracite-950 hover:bg-indigo-600/30 border border-anthracite-600 text-indigo-300 font-medium transition">
|
||||
Trunk avec Enregistrement (REGISTER + Auth)
|
||||
</button>
|
||||
<button type="button" onclick="applyTrunkArchitecture('gateway_receive')" class="px-2.5 py-1 rounded-lg bg-anthracite-950 hover:bg-emerald-600/30 border border-emerald-500/50 text-emerald-300 font-medium transition flex items-center gap-1">
|
||||
<span>📟 Passerelle FXO / Analogique (HT813, Livebox)</span>
|
||||
</button>
|
||||
<button type="button" onclick="applyTrunkArchitecture('ip_peering')" class="px-2.5 py-1 rounded-lg bg-anthracite-950 hover:bg-indigo-600/30 border border-anthracite-600 text-purple-300 font-medium transition">
|
||||
Peering IP / SBC / Passerelle (Sans REGISTER)
|
||||
</button>
|
||||
@@ -3340,16 +3343,78 @@ $currentUser = Auth::getUsername();
|
||||
|
||||
<!-- SOUS-ONGLET 1 : SERVEUR, AUTHENTIFICATION & ROUTAGE DIRECT -->
|
||||
<div id="trunk-panel-general" class="trunk-subpanel space-y-4">
|
||||
<!-- Mode d'Enregistrement SIP (Send / Receive / None) -->
|
||||
<div class="p-4 rounded-xl bg-anthracite-950 border border-anthracite-700 space-y-3">
|
||||
<div class="flex flex-wrap items-center justify-between gap-2">
|
||||
<span class="text-xs font-bold uppercase tracking-wider text-emerald-300">Mode d'Enregistrement SIP (Registration)</span>
|
||||
<span class="text-[11px] text-slate-400">Définit le sens de l'enregistrement ou le mode peering</span>
|
||||
</div>
|
||||
<div class="grid grid-cols-1 sm:grid-cols-3 gap-3 text-xs">
|
||||
<label class="flex items-start gap-2.5 p-3 rounded-xl bg-anthracite-900 border border-anthracite-700 cursor-pointer hover:border-indigo-500/50 transition">
|
||||
<input type="radio" name="registration_mode" value="send" checked onchange="onTrunkRegistrationModeChange(this.value)" class="mt-0.5">
|
||||
<div>
|
||||
<div class="text-white font-semibold flex items-center gap-1.5">
|
||||
<span>Émission (Send)</span>
|
||||
<span class="text-[10px] px-1.5 py-0.2 rounded bg-indigo-500/20 text-indigo-300">Défaut</span>
|
||||
</div>
|
||||
<div class="text-[11px] text-slate-400 mt-0.5">Asterisk s'enregistre vers l'opérateur (OVH, Keyyo...). Serveur SIP requis.</div>
|
||||
</div>
|
||||
</label>
|
||||
<label class="flex items-start gap-2.5 p-3 rounded-xl bg-anthracite-900 border border-emerald-500/40 cursor-pointer hover:border-emerald-500 transition">
|
||||
<input type="radio" name="registration_mode" value="receive" onchange="onTrunkRegistrationModeChange(this.value)" class="mt-0.5">
|
||||
<div>
|
||||
<div class="text-emerald-300 font-semibold flex items-center gap-1.5">
|
||||
<span>Réception (Receive)</span>
|
||||
<span class="text-[10px] px-1.5 py-0.2 rounded bg-emerald-500/20 text-emerald-300">Passerelle FXO</span>
|
||||
</div>
|
||||
<div class="text-[11px] text-slate-300 mt-0.5">La passerelle s'enregistre sur Asterisk (Grandstream HT813, Livebox). Hôte libre.</div>
|
||||
</div>
|
||||
</label>
|
||||
<label class="flex items-start gap-2.5 p-3 rounded-xl bg-anthracite-900 border border-anthracite-700 cursor-pointer hover:border-purple-500/50 transition">
|
||||
<input type="radio" name="registration_mode" value="none" onchange="onTrunkRegistrationModeChange(this.value)" class="mt-0.5">
|
||||
<div>
|
||||
<div class="text-purple-300 font-semibold flex items-center gap-1.5">
|
||||
<span>Aucun (None)</span>
|
||||
<span class="text-[10px] px-1.5 py-0.2 rounded bg-purple-500/20 text-purple-300">Peering IP</span>
|
||||
</div>
|
||||
<div class="text-[11px] text-slate-400 mt-0.5">Interconnexion directe sans enregistrement (Trunk IP dédié, SBC). Serveur SIP requis.</div>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<!-- Guide interactif d'aide pour Grandstream HT813 / Livebox FXO -->
|
||||
<div id="trunk-receive-guide" class="hidden p-3.5 rounded-xl bg-emerald-950/40 border border-emerald-500/40 text-xs text-slate-200 space-y-2">
|
||||
<div class="flex items-center gap-2 font-bold text-emerald-300 text-xs">
|
||||
<span>💡 Guide Rapide : Paramétrage de votre Grandstream HT813 / HT503 (FXO)</span>
|
||||
</div>
|
||||
<p class="text-[11px] text-slate-300 leading-relaxed">
|
||||
Dans l'interface web du boîtier HT813 (onglet <strong>FXO Port</strong> relié à la prise téléphone verte de votre Livebox / Box opérateur) :
|
||||
</p>
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 gap-2 text-[11px] font-mono bg-anthracite-950 p-3 rounded-lg border border-emerald-500/25">
|
||||
<div>• <strong>Primary SIP Server</strong> : <span class="text-emerald-300 font-bold"><?= htmlspecialchars($_SERVER['SERVER_ADDR'] ?? '10.10.175.204') ?>:5060</span></div>
|
||||
<div>• <strong>SIP Registration</strong> : <span class="text-emerald-300 font-bold">Yes</span></div>
|
||||
<div>• <strong>SIP User ID</strong> : <span class="text-indigo-300 font-bold" id="ht813-guide-user">Même valeur que Username</span></div>
|
||||
<div>• <strong>Authenticate ID</strong> : <span class="text-indigo-300 font-bold" id="ht813-guide-auth">Même valeur que Username</span></div>
|
||||
<div>• <strong>Authenticate Password</strong> : <span class="text-indigo-300 font-bold">Votre mot de passe secret</span></div>
|
||||
<div>• <strong>Stage Method (1/2)</strong> : <span class="text-slate-300">1 (Recommandé pour appel direct)</span></div>
|
||||
<div class="sm:col-span-2 text-slate-300 font-sans border-t border-anthracite-800 pt-2 mt-1">
|
||||
• <strong>Unconditional Call Forward to VoIP</strong> : Définir <em>User ID</em> = votre numéro (ex: <code class="text-amber-300 font-mono">0494679572</code>) et <em>SIP Server</em> = <code class="text-amber-300 font-mono">ch1:<?= htmlspecialchars($_SERVER['SERVER_ADDR'] ?? '10.10.175.204') ?>:5060;</code>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 sm:grid-cols-4 gap-4">
|
||||
<div class="sm:col-span-1">
|
||||
<label class="block text-xs font-medium text-slate-200 mb-1">Nom du Trunk * <span class="text-slate-400">(ID PJSIP)</span></label>
|
||||
<input type="text" name="name" placeholder="ex: mon-trunk-sip" required
|
||||
<input type="text" name="name" placeholder="ex: mon-trunk-sip ou 0494679572" required
|
||||
class="w-full px-3 py-2 rounded-lg bg-anthracite-950 border border-anthracite-700 text-sm text-white font-mono">
|
||||
</div>
|
||||
<div class="sm:col-span-2">
|
||||
<label class="block text-xs font-medium text-slate-200 mb-1">Serveur SIP / SBC / Passerelle * <span class="text-slate-400">(Host / IP)</span></label>
|
||||
<input type="text" name="host" placeholder="ex: sbc.operateur.net ou 192.168.1.250" required oninput="syncTrunkHostDomain(this.value)"
|
||||
<label id="trunk-host-label" class="block text-xs font-medium text-slate-200 mb-1">Serveur SIP / SBC / Passerelle * <span class="text-slate-400">(Host / IP)</span></label>
|
||||
<input type="text" name="host" id="trunk-host-input" placeholder="ex: sbc.operateur.net ou 192.168.1.250" required oninput="syncTrunkHostDomain(this.value)"
|
||||
class="w-full px-3 py-2 rounded-lg bg-anthracite-950 border border-anthracite-700 text-sm text-white font-mono">
|
||||
<div id="trunk-host-hint" class="hidden text-[10px] text-emerald-400 mt-1">En mode Réception (Receive), l'hôte peut rester vide (découverte dynamique du HT813 lors du REGISTER).</div>
|
||||
</div>
|
||||
<div class="sm:col-span-1">
|
||||
<label class="block text-xs font-medium text-slate-200 mb-1">Port SIP * <span class="text-slate-400">(Port)</span></label>
|
||||
@@ -3520,8 +3585,13 @@ $currentUser = Auth::getUsername();
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- SOUS-ONGLET 2 : ENREGISTREMENT (REGISTRATION) & IDENTITÉ SIP -->
|
||||
<!-- SOUS-ONGLET 3 : ENREGISTREMENT (REGISTRATION) & IDENTITÉ SIP -->
|
||||
<div id="trunk-panel-identity" class="trunk-subpanel hidden space-y-4">
|
||||
<div id="trunk-reg-outbound-notice" class="hidden p-3.5 rounded-xl bg-indigo-950/40 border border-indigo-500/40 text-xs text-indigo-200 flex items-center gap-2">
|
||||
<span>ℹ️</span>
|
||||
<span>En mode <strong>Réception (Passerelle type HT813)</strong> ou <strong>Peering IP direct (None)</strong>, Asterisk n'émet aucun SIP REGISTER sortant. Les paramètres d'enregistrement ci-dessous ne s'appliquent qu'en mode <strong>Émission (Send)</strong>.</span>
|
||||
</div>
|
||||
|
||||
<div class="p-4 rounded-xl bg-anthracite-950 border border-anthracite-700 space-y-4">
|
||||
<div class="flex flex-wrap items-center justify-between gap-2">
|
||||
<span class="text-xs font-bold uppercase tracking-wider text-emerald-300">Paramètres d'Enregistrement SIP (<code class="text-emerald-300">[type = registration]</code>)</span>
|
||||
|
||||
Reference in new issue
Block a user