@extends('layouts.app')
@section('title', $service->service_number)
@section('content')
@if ($service->status === 'pending' || $service->status === 'provisioning')
Activate service
@endif
@if ($service->status === 'active' || $service->status === 'grace')
Suspend
@endif
@if ($service->status === 'suspended')
@endif
@if ($service->is_online)
@endif
Change plan
@include('components.kpi', ['label' => 'Monthly charge', 'value' => money($service->effective_price, $service->customer->currency),
'variant' => 'good', 'href' => '#billing'])
@include('components.kpi', ['label' => 'Outstanding', 'value' => money($service->outstanding, $service->customer->currency),
'variant' => $service->outstanding > 0 ? 'bad' : 'good', 'href' => '#billing'])
@include('components.kpi', ['label' => 'Next billing', 'value' => $service->next_billing_date?->format('d M Y') ?? '—', 'href' => '#billing'])
@include('components.kpi', ['label' => 'Session uptime', 'value' => $service->currentSession?->duration_label ?? '—',
'variant' => $service->is_online ? 'good' : '', 'href' => '#sessions'])
@include('components.kpi', ['label' => 'Data this period', 'value' => human_bytes($usage['total_bytes']),
'variant' => 'info', 'href' => '#usage'])
@include('components.kpi', ['label' => 'Quota used', 'value' => $service->quota_used_percent !== null ? percent($service->quota_used_percent) : 'Unlimited',
'variant' => ($service->quota_used_percent ?? 0) > 90 ? 'warn' : 'info', 'href' => '#usage'])
@if ($service->provisioning_status !== 'active' && $service->status !== 'cancelled')
ⓘ
Provisioning state: {{ title_case_words($service->provisioning_status) }}.
The RADIUS record is {{ $service->radius_synced ? 'pushed' : 'not yet pushed' }}.
Activate the service once the technician has signed off the installation.
Activate
@endif
{{-- ============ OVERVIEW ============ --}}
Connection details
{{ title_case_words($service->service_type) }}
Plan {{ $service->plan?->name }}
Speed profile {{ $service->speedProfile?->name ?? 'Inherited from plan' }}
Download / upload
{{ $service->download_kbps ? round($service->download_kbps/1024).' / '.round($service->upload_kbps/1024).' Mbps' : '—' }}
Monthly quota
{{ $service->monthly_quota_gb ? $service->monthly_quota_gb.' GB' : 'Unlimited' }}
Static IP {{ $service->static_ip ?: 'Dynamic' }}
IP pool {{ $service->ipPool?->name ?: '—' }}
MAC address {{ $service->mac_address ?: '—' }}
NAS / BNG {{ $service->nas_ip ?: 'Auto-detect' }}
Priority {{ $service->speedProfile?->priority ?? 8 }} (1 = highest)
Installation
@if ($service->onu)
ONU {{ $service->onu->optical_health }}
@endif
Address {{ $service->installation_address }}
City / region {{ trim(($service->city ?? '').' '.($service->region ?? '')) ?: '—' }}
Coordinates
{{ $service->latitude && $service->longitude ? $service->latitude.', '.$service->longitude : '—' }}
OLT {{ $service->olt_name ?: ($service->onu?->pon?->olt?->name ?? '—') }}
PON port {{ $service->pon_port ?: ($service->onu?->pon?->pon_port ?? '—') }}
Splitter {{ $service->splitter ?: '—' }}
CPE model {{ $service->cpe_model ?: '—' }}
CPE serial {{ $service->cpe_serial ?: '—' }}
CPE MAC {{ $service->cpe_mac ?: '—' }}
@if ($service->onu)
Rx / Tx power
{{ $service->onu->rx_power_dbm }} / {{ $service->onu->tx_power_dbm }} dBm
Distance {{ $service->onu->distance_km }} km
@endif
Billing calendar
Billing period {{ title_case_words($service->billing_period) }}
Billing cycle {{ title_case_words($service->billing_cycle) }}
List price {{ money($service->price, $service->customer->currency) }}
@if ($service->discount_amount > 0)
Discount −{{ money($service->discount_amount, $service->customer->currency) }}
@if ($service->discount_ends_on)until {{ $service->discount_ends_on->format('d M Y') }} @endif
@endif
Effective price {{ money($service->effective_price, $service->customer->currency) }}
Add-ons {{ money($service->addons_total, $service->customer->currency) }}/month
Start date {{ $service->start_date->format('d M Y') }}
Next billing {{ $service->next_billing_date?->format('d M Y') ?: 'Not scheduled' }}
Payment due {{ $service->next_due_date?->format('d M Y') ?: '—' }}
Auto renew {{ $service->auto_renew ? 'Enabled' : 'Disabled' }}
Contract
{{ $service->contract_months ? $service->contract_months.' months — '.title_case_words($service->contract_status) : 'No contract' }}
@if ($service->contract_ends_on)Ends {{ $service->contract_ends_on->format('d M Y') }}
@endif
SLA tier {{ title_case_words($service->sla_tier) }}
Quick actions
AAA sync
{{ $service->radius_synced ? 'synced '.($service->radius_synced_at?->diffForHumans() ?? '') : 'not synced' }}
Reset password
Change speed (CoA)
Raise invoice
Active add-ons
@forelse ($service->addons as $addon)
{{ $addon->addon?->name }}
{{ money($addon->price, $service->customer->currency) }}
@empty
No add-ons attached.
@endforelse
Internal notes
@if ($notes->isNotEmpty())
@foreach ($notes as $note)
{{ $note->user?->name ?? 'System' }}
{{ $note->type }}
{{ $note->created_at->format('d M Y H:i') }}
{{ $note->note }}
@endforeach
@else
No notes recorded yet.
@endif
{{-- ============ SESSIONS ============ --}}
RADIUS sessions
Started Stopped Duration IP NAS
Down Up Total Terminated
@forelse ($sessions as $session)
@if ($session->is_online) @endif
{{ $session->started_at?->format('d M H:i') ?: '—' }}
{{ $session->stopped_at?->format('d M H:i') ?: '—' }}
{{ $session->duration_label }}
{{ $session->framed_ip ?: '—' }}
{{ $session->nas_ip }}
{{ human_bytes($session->input_octets) }}
{{ human_bytes($session->output_octets) }}
{{ human_bytes($session->total_octets) }}
{{ $session->terminate_cause ?: '—' }}
@empty
@include('components.empty-state', ['icon' => '⏱', 'title' => 'No sessions recorded',
'message' => 'Sessions appear once the NAS sends RADIUS accounting (radacct).'])
@endforelse
@if ($sessions->hasPages())@endif
Authentication log
From FreeRADIUS radpostauth
When Result NAS Assigned IP
@forelse ($authLogs as $log)
{{ $log->created_at->format('d M Y H:i:s') }}
{{ $log->reply }}
{{ $log->nas_ip ?: '—' }}
{{ $log->framed_ip ?: '—' }}
@empty
@include('components.empty-state', ['icon' => '🔒', 'title' => 'No authentication attempts logged'])
@endforelse
{{-- ============ USAGE ============ --}}
@include('components.kpi', ['label' => 'Total this period', 'value' => human_bytes($usage['total_bytes']), 'variant' => 'info', 'href' => '#'])
@include('components.kpi', ['label' => 'Download', 'value' => human_bytes($usage['input_bytes']), 'href' => '#'])
@include('components.kpi', ['label' => 'Upload', 'value' => human_bytes($usage['output_bytes']), 'href' => '#'])
@include('components.kpi', ['label' => 'Online time', 'value' => $usage['online_hours'].' h',
'delta' => $usage['sessions'].' sessions', 'href' => '#'])
Daily consumption — last 30 days
@include('components.chart', [
'series' => collect($usageSeries)->map(fn ($p) => [
'label' => \Carbon\Carbon::parse($p['date'])->format('d M'),
'value' => $p['bytes'],
])->all(),
'type' => 'bar', 'format' => 'bytes', 'height' => 210,
])
{{-- ============ BILLING ============ --}}
Invoices for this service
+ Raise invoice
Invoice Period Issued Due
Total Balance Status
@forelse ($invoices as $invoice)
{{ $invoice->invoice_number }}
{{ $invoice->period_start?->format('d M') ?? '—' }} – {{ $invoice->period_end?->format('d M Y') ?? '—' }}
{{ $invoice->issue_date->format('d M Y') }}
{{ $invoice->due_date->format('d M Y') }}
{{ money($invoice->total, $invoice->currency) }}
{{ money($invoice->balance, $invoice->currency) }}
{{ $invoice->status }}
View
@if ($invoice->balance > 0)
Collect
@endif
@empty
@include('components.empty-state', ['icon' => '📄', 'title' => 'No invoices yet'])
@endforelse
{{-- ============ AAA / RADIUS ============ --}}
AAA record
@if ($service->radiusUser)
Username {{ $service->radiusUser->username }}
Auth type {{ $service->radiusUser->auth_type }}
Realm {{ $service->radiusUser->realm ?: '—' }}
Group {{ $service->radiusUser->groupname ?: '—' }}
Framed pool {{ $service->radiusUser->framed_pool ?: '—' }}
Static IP {{ $service->radiusUser->static_ip ?: '—' }}
Status {{ $service->radiusUser->status }}
Last sync
{{ $service->radiusUser->last_sync_status }}
{{ $service->radiusUser->last_synced_at?->diffForHumans() }}
@if ($service->radiusUser->last_sync_error)
Error {{ $service->radiusUser->last_sync_error }}
@endif
@else
@include('components.empty-state', ['icon' => '🔒', 'title' => 'No AAA record',
'message' => 'Push the record to FreeRADIUS to allow authentication.',
'action' => '
'])
@endif
CoA / Disconnect history
All →
When Type Purpose NAS Result
@forelse ($coaHistory as $req)
{{ $req->created_at->format('d M H:i') }}
{{ $req->type }}
{{ title_case_words($req->purpose) }}
{{ $req->nas_ip }}
{{ $req->status }}
@if ($req->response_detail)
{{ \Illuminate\Support\Str::limit($req->response_detail, 60) }}
@endif
@empty
@include('components.empty-state', ['icon' => '⚡', 'title' => 'No CoA or DM requests sent'])
@endforelse
{{-- ============ HISTORY ============ --}}
Status transitions
@if ($history->isNotEmpty())
@foreach ($history as $entry)
{{ title_case_words($entry->from_status ?? 'new') }} →
{{ title_case_words($entry->to_status) }}
{{ $entry->created_at->format('d M Y H:i') }}
@if ($entry->user) · {{ $entry->user->name }} @endif
@if ($entry->reason)
{{ $entry->reason }}
@endif
@endforeach
@else
No transitions recorded.
@endif
Plan & service changes
When Type Change Prorated Status
@forelse ($changes as $change)
{{ $change->created_at->format('d M Y') }}
{{ title_case_words($change->change_type) }}
{{ $change->old_values['plan'] ?? '—' }} → {{ $change->new_values['plan'] ?? '—' }}
{{ (float) $change->prorated_amount > 0 ? money($change->prorated_amount) : '—' }}
{{ $change->status }}
@empty
@include('components.empty-state', ['icon' => '⇅', 'title' => 'No changes made'])
@endforelse
Cancellation
@if (in_array($service->status, ['cancelled', 'terminated']))
⚠
{{ title_case_words($service->status) }}
on {{ $service->cancelled_at?->format('d M Y H:i') }}.
Reason: {{ $service->cancellationReason?->reason ?: 'Not specified' }}.
@if ($service->cancellation_notes)
{{ $service->cancellation_notes }}
@endif
@else
@endif
{{-- ============ MODALS ============ --}}
@csrf
Activate service
×
Record the installation details and start the billing calendar.
@csrf
Change plan
×
Current plan: {{ $service->plan?->name }}
at {{ money($service->price, $service->customer->currency) }}.
New plan *
Select a plan…
@foreach ($plans as $plan)
id === $service->plan_id)>
{{ $plan->name }} — {{ money($plan->price) }} ({{ $plan->speed_label }})
@endforeach
Prorate the unused days of the current period
The live session is re-shaped immediately via RADIUS CoA.
@csrf
Reset RADIUS password
×
@csrf
Change live speed (CoA)
×
Sends a Change-of-Authorization to the NAS. The subscriber stays connected.
@endsection
@push('scripts')
@endpush