@extends('layouts.app') @php $deviceType = $endpoint->description ?? 'sip_phone'; $deviceLabels = [ 'sip_phone' => ['SIP Phone', 'blue', 'phone'], 'web_phone' => ['Web Phone', 'cyan', 'monitor'], 'mobile_app' => ['Mobile App', 'violet', 'smartphone'], 'teams' => ['Teams', 'violet', 'users'], ]; $dtInfo = $deviceLabels[$deviceType] ?? $deviceLabels['sip_phone']; $extId = $endpoint->id ?? ''; $isWebRTC = in_array($deviceType, ['web_phone', 'mobile_app']); $transportLabel = match (true) { str_contains((string)($endpoint->transport ?? ''), 'wss') => 'WSS / TLS', str_contains((string)($endpoint->transport ?? ''), 'ws') => 'WS', str_contains((string)($endpoint->transport ?? ''), 'tcp') => 'TCP', default => 'UDP', }; $typeColors = [ 'blue' => ['bg' => 'rgba(37,99,235,.1)', 'color' => '#2563eb'], 'cyan' => ['bg' => 'rgba(6,182,212,.1)', 'color' => '#0891b2'], 'violet' => ['bg' => 'rgba(124,58,237,.1)', 'color' => '#7c3aed'], ]; $tc = $typeColors[$dtInfo[1]] ?? $typeColors['blue']; // Routing variables $rMode = $routing->routing_mode ?? 'system_default'; $rPrimary = $routing->outbound_trunk_id ?? null; $rFailover = $routing->failover_trunk_id ?? null; $rFailEn = (bool) ($routing->enable_trunk_failover ?? false); @endphp @section('content') {{-- ── Page header ── --}}

Extension {{ $extId }}

@if($extRow->name ?? '') · {{ $extRow->name }} @endif
{{ $dtInfo[0] }}  ·  {{ $transportLabel }}  ·  {{ $endpoint->context ?? 'from-internal' }}
@if(Route::has('asterisk.reload'))
@csrf
@endif Back to List
{{-- ── Alerts ── --}} @if(session('status')) @endif @if($errors->any()) @endif {{-- ── Tab navigation ── --}}
@csrf {{-- ══════════════════════════════════════════════════ --}} {{-- TAB: GENERAL --}} {{-- ══════════════════════════════════════════════════ --}}
{{-- Status strip --}}
DND {{ ($features->dnd ?? false) ? 'ON' : 'OFF' }} Recording {{ ($features->record_calls ?? false) ? 'ON' : 'OFF' }} CFU {{ ($features->cfu_enabled ?? false) ? 'ON' : 'OFF' }} CFB {{ ($features->cfb_enabled ?? false) ? 'ON' : 'OFF' }} CFN {{ ($features->cfn_enabled ?? false) ? 'ON' : 'OFF' }} Follow Me {{ ($features->follow_me ?? false) ? 'ON' : 'OFF' }}
AOR: {{ $endpoint->aors ?? $extId }} Max Contacts: {{ $aor->max_contacts ?? 1 }} WebRTC: {{ ($endpoint->webrtc ?? 'no') === 'yes' ? 'Yes' : 'No' }} @if($endpoint->voicemail_extension ?? '')VM: {{ $endpoint->voicemail_extension }}@endif
{{-- Main two-column form --}}
{{-- LEFT: Identity --}}
Identity
{{ $extId }}
@if($departments->count())
@foreach($departments as $dept) @php $checked = in_array((string)$dept->id, old('department_ids', $extDepartments)); @endphp @endforeach
@endif
{{-- RIGHT: Device Configuration --}}
Device Configuration
{{-- /row --}}
{{-- ══════════════════════════════════════════════════ --}} {{-- TAB: AUTHENTICATION --}} {{-- ══════════════════════════════════════════════════ --}}
SIP Credentials
Username and password for device registration
Username matches the extension number.
@error('password')
{{ $message }}
@enderror
Minimum 8 characters recommended. Used for SIP device registration.
AOR Settings
Address of Record — contact capacity
@error('max_contacts')
{{ $message }}
@enderror
Number of devices that can register simultaneously.
Desk phones: 1 (default). WebRTC/Webphone: 5 recommended (allows multiple browser tabs). Mobile app: 3 recommended (phone + tablet + backup).
{{-- ══════════════════════════════════════════════════ --}} {{-- TAB: CALL FEATURES --}} {{-- ══════════════════════════════════════════════════ --}}
{{-- ── Group 1: Quick Toggles (compact tile grid) ── --}}
Quick Toggles
One-tap call handling for this extension. Changes save immediately.
@php $cwOn = ($features->call_waiting ?? 1); $bsOn = (bool) old('boss_secretary', $features->boss_secretary ?? false); $bsSec = old('secretary_ext', $features->secretary_ext ?? ''); @endphp
{{-- Do Not Disturb --}}
{{ ($features->dnd ?? false) ? 'Enabled' : 'Disabled' }}
dnd ?? false) ? 'checked' : '' }} onchange="toggleFeature('dnd', this.checked)">
Do Not Disturb (DND)
Reject all incoming calls. Callers hear a busy signal.
{{-- Call Waiting (default ON; turn OFF for agents so a 2nd call gets busy) --}}
{{ $cwOn ? 'Enabled' : 'Disabled' }}
Call Waiting
Allow a second call while already on a call. Turn off for agent extensions so a second caller gets busy instead of a beep.
{{-- Call Recording --}}
{{ ($features->record_calls ?? false) ? 'Enabled' : 'Disabled' }}
record_calls ?? false) ? 'checked' : '' }} onchange="toggleFeature('record_calls', this.checked)">
Call Recording
Record all inbound and outbound calls for this extension.
{{-- Require PIN for outbound --}}
{{ ($endpoint->require_pin ?? false) ? 'Required' : 'Not required' }}
require_pin ?? false) ? 'checked' : '' }} onchange="toggleRequirePin(this.checked)">
Require PIN for Outbound
Must enter a valid PIN before any outbound call connects. Manage codes under Applications → PIN Codes.
{{-- Boss / Secretary Screening (expands fields below the grid) --}}
Boss / Secretary Screening
Incoming calls ring the chosen secretary instead. Whitelisted callers still reach the boss directly. Saved on Save.
@forelse($bossWhitelist as $wl) @empty @endforelse
NumberDescription
{{ $wl->number }} {{ $wl->description }}
No whitelist numbers yet.
Whitelist changes save immediately (no need to click Save).
{{-- ── Group 2: Ring & Routing (two cards side by side) ── --}}
{{-- Ring time + Follow Me --}}
Ring & Follow Me
How long this extension rings, and where else to ring.
{{-- Ring Time (live-saves via AJAX) --}}
Ring Time
How long this extension's phones ring before voicemail or no-answer forward. Leave blank to use the system default (currently {{ $ringTimeDefault ?? 30 }} seconds).
sec {{ $ringTimeOverride !== null ? 'Custom' : 'Default' }}
{{-- Follow Me --}}
Follow Me
Ring a secondary number simultaneously with this extension's phones. Internal (e.g. 1003) or external (e.g. 0824989396, +27824989396).
{{ ($features->follow_me ?? false) ? 'Enabled' : 'Disabled' }}
follow_me ?? false) ? 'checked' : '' }} onchange="toggleFeature('follow_me', this.checked)">
{{-- Call Forwarding --}}
Call Forwarding
Forward calls under specific conditions.
{{-- CFU --}}
Call Forward — Unconditional (CFU)
Forward all incoming calls to another number immediately.
{{ ($features->cfu_enabled ?? false) ? 'Enabled' : 'Disabled' }}
cfu_enabled ?? false) ? 'checked' : '' }} onchange="toggleFeature('cfu_enabled', this.checked)">
{{-- CFB --}}
Call Forward — Busy (CFB)
Forward calls when the extension is busy on another call.
{{ ($features->cfb_enabled ?? false) ? 'Enabled' : 'Disabled' }}
cfb_enabled ?? false) ? 'checked' : '' }} onchange="toggleFeature('cfb_enabled', this.checked)">
{{-- CFN --}}
Call Forward — No Answer (CFN)
Forward calls when unanswered after a timeout period.
Triggers after Ring Time ({{ $ringTimeOverride ?? $ringTimeDefault ?? 30 }}s)
@php $cfnaScope = $features->cfna_scope ?? 'inherit'; @endphp Limit no-answer forwarding to external (trunk) calls.
{{ ($features->cfn_enabled ?? false) ? 'Enabled' : 'Disabled' }}
cfn_enabled ?? false) ? 'checked' : '' }} onchange="toggleFeature('cfn_enabled', this.checked)">
{{-- CFI --}}
Call Forward — Invalid / Unavailable (CFI)
Forward calls when the extension is unreachable or unregistered.
{{ ($features->cfi_enabled ?? false) ? 'Enabled' : 'Disabled' }}
cfi_enabled ?? false) ? 'checked' : '' }} onchange="toggleFeature('cfi_enabled', this.checked)">
@push('scripts') @endpush {{-- ══════════════════════════════════════════════════ --}} {{-- TAB: VOICEMAIL --}} {{-- ══════════════════════════════════════════════════ --}}
@php $vmEnabled = (bool)($extRow->has_voicemail ?? ($voicemail ? true : false)); $vmEmail = old('vm_email', $voicemail->email ?? $extRow->email ?? ''); $vmPin = old('vm_pin', $voicemail->password ?? ''); $vmAttach = old('vm_attach', $voicemail->attach ?? 'yes'); @endphp
{{-- Enable / Disable --}}
Voicemail
Mailbox and notification settings
{{-- Toggle --}}
Enable Voicemail
Creates mailbox {{ $extId }}@@default and activates unanswered-call routing.
{{-- Fields shown only when enabled --}}

4–10 digits. Used to access the mailbox by phone (*97).
New voicemail notifications (and optional audio attachment) sent here.
Attach the recorded message as a .wav file to the email notification.
Feature code to dial to check voicemail. Default is *97.
Mailbox Status
Current voicemail box info
@if($voicemail)
Mailbox {{ $voicemail->mailbox }}@{{ $voicemail->context }}
Full Name {{ $voicemail->fullname ?: '—' }}
Email {{ $voicemail->email ?: '—' }}
Attach Audio {{ ucfirst($voicemail->attach ?? 'yes') }}
@else
No voicemail box configured.
Enable voicemail on the left to create one.
@endif
{{-- Email Notifications Section --}}
Email Notifications
Missed call and event alerts for this extension
@php $mcNotify = old('missed_call_notify', $endpoint->missed_call_notify ?? false); $mcEmail = old('missed_call_email', $endpoint->missed_call_email ?? ''); @endphp
Missed Call Email Alert
Send an email when an inbound call to this extension is not answered.
Leave blank to use the voicemail email address above. Notifications include caller ID, time, and ring duration.
Advanced PJSIP Settings
Read-only view of effective endpoint configuration
@foreach([ 'Transport' => $endpoint->transport ?? '—', 'Direct Media' => $endpoint->direct_media ?? '—', 'RTP Symmetric' => $endpoint->rtp_symmetric ?? '—', 'Force RPORT' => $endpoint->force_rport ?? '—', 'Rewrite Contact' => $endpoint->rewrite_contact ?? '—', 'DTLS' => $endpoint->dtls_auto_generate_cert ?? '—', 'Media Encrypt' => $endpoint->media_encryption ?? '—', 'WebRTC' => $endpoint->webrtc ?? '—', 'ICE Support' => $endpoint->ice_support ?? '—', 'AVPF' => $endpoint->avpf ?? '—', 'RTCP MUX' => $endpoint->rtcp_mux ?? '—', 'Bundle' => $endpoint->bundle ?? '—', 'DTMF Mode' => $endpoint->dtmf_mode ?? '—', ] as $label => $val)
{{ $label }}
{{ $val }}
@endforeach
{{-- Editable codec (allow) list — live-saves via /api/extensions/{ext}/codecs --}} @php $availableCodecs = ['ulaw','alaw','g729','g722','gsm','ilbc','opus','speex']; $currentCodecs = array_values(array_filter(array_map('trim', explode(',', (string)($endpoint->allow ?? ''))))); @endphp
Audio Codecs
Tick the codecs this extension may use, in order of preference. Changes save immediately. Order is set by the sequence you tick them.
@foreach($availableCodecs as $codec) @php $pos = array_search($codec, $currentCodecs, true); @endphp @endforeach
Note: Encryption, ICE and transport are set automatically by the Device Type on the General tab. Only the audio codecs above can be tuned per extension.
{{-- ══════════════════════════════════════════════════ --}} {{-- TAB: ROUTING --}} {{-- ══════════════════════════════════════════════════ --}} @php $rMode = old('routing_mode', $routing->routing_mode ?? 'system_default'); $rPrimary = old('outbound_trunk_id', $routing->outbound_trunk_id ?? ''); $rFailover = old('failover_trunk_id', $routing->failover_trunk_id ?? ''); $rFailEn = (bool) old('enable_trunk_failover', $routing->enable_trunk_failover ?? false); @endphp
{{-- Card 1: Outbound Routing --}}
Outbound Routing
Control which SIP trunk carries this extension's outbound calls
Determines how outbound calls from this extension are routed.
If the primary trunk is unavailable, automatically route calls via the failover trunk.
@if($trunks->isEmpty())
No SIP trunks are configured yet. Create a trunk before assigning dedicated routing.
@endif
{{-- Test Routing --}}
{{-- Card 2: Routing Summary --}}
Routing Summary
Live preview of the current configuration
Mode {{ \App\Models\ExtensionRouting::modeLabel($rMode) }}
Primary {{ $trunkLabel($rPrimary) ?: 'Not set' }}
Failover {{ $trunkLabel($rFailover) ?: 'None' }}
Failover Enabled {{ $rFailEn ? 'Yes' : 'No' }}
{{-- ── Sticky action bar ── --}}
Cancel
{{-- ══════════════════════════════════════════════════ --}} {{-- TAB: DEVICES (outside main form to avoid nesting) --}} {{-- ══════════════════════════════════════════════════ --}}
Registered Devices
All devices that ring when this extension is called. Add SIP phones, web phones, or mobile apps.
@forelse($devices ?? collect() as $dev) @php $typeIcon = match($dev->device_type) { 'web_phone' => 'monitor', 'mobile_app' => 'smartphone', 'teams' => 'users', default => 'phone', }; $typeColor = match($dev->device_type) { 'web_phone' => '#0891b2', 'mobile_app' => '#7c3aed', 'teams' => '#6366f1', default => '#2563eb', }; $regColor = $dev->reg_status === 'Registered' ? '#16a34a' : '#94a3b8'; @endphp @empty @endforelse
Device Username Type Status IP / User Agent Ring Actions
{{ $dev->label ?? ucfirst(str_replace('_', ' ', $dev->device_type)) }}
@if($dev->is_primary) PRIMARY @endif
{{ $dev->endpoint_id }} {{ ucfirst(str_replace('_', ' ', $dev->device_type)) }} {{ $dev->reg_status }} @if($dev->reg_ip) {{ $dev->reg_ip }} @endif @if($dev->user_agent)
{{ $dev->user_agent }}
@endif @if(!$dev->reg_ip && !$dev->user_agent) — @endif
@if($dev->is_teams_virtual ?? false) {{ $dev->ring_on_incoming ? 'YES' : 'NO' }} @else
ring_on_incoming ? 'checked' : '' }} data-device-id="{{ $dev->id }}" data-ext="{{ $extId }}" style="cursor:pointer;">
@endif
@if($dev->is_teams_virtual ?? false) Manage @elseif(!$dev->is_primary) @if($dev->device_type === 'mobile_app') @endif
@csrf @method('DELETE')
@else @if($dev->device_type === 'mobile_app') @else — @endif @endif
No devices mapped. The primary device will be created automatically.
{{-- Add device form --}}
Add Device
Create a new PJSIP endpoint for this extension. Same credentials, different transport settings.
@csrf
How it works: Each device gets its own PJSIP endpoint with the correct transport (UDP for SIP phones, WSS for WebRTC). All devices share the same password and will ring simultaneously on incoming calls. Teams devices are managed separately under Trunks → MS Teams.
{{-- ══════════════════════════════════════════════════ --}} {{-- TAB: QUEUE MEMBERSHIP (outside main form, AJAX-driven) --}} {{-- ══════════════════════════════════════════════════ --}}
Queue Membership
Add this extension to one or more call-center queues. Toggle on/off — saves immediately.
@if(empty($availableQueues ?? []))
No call-center queues configured. Create one under Call Center → Queues.
@else
Tick a queue to add this extension as an agent. The system creates an agent record automatically using the extension's display name. Untick to remove.
@foreach($availableQueues as $q) @php $member = collect($queueMembership ?? [])->firstWhere('queue_id', $q->id); $isMember = (bool) $member; $isPaused = $member && (int) $member->is_paused === 1; @endphp
{{ $q->name }}
{{ $q->number }} · {{ $q->strategy }}
@if($isMember)
@endif
@endforeach
@endif
{{-- ── Mobile Setup Modal ── --}} @endsection