605 lines
19 KiB
PHP
605 lines
19 KiB
PHP
@extends('layouts.master')
|
|
|
|
@section('title', 'DHCP Reservations')
|
|
|
|
@section('content')
|
|
|
|
<div class="container-fluid py-4">
|
|
{{-- Header --}}
|
|
<div class="d-flex justify-content-between align-items-center mb-4">
|
|
<div>
|
|
<h4 class="mb-1">DHCP Reservations</h4>
|
|
<div class="text-muted small">Manage static DHCP leases by MAC address</div>
|
|
</div>
|
|
<button type="button" class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#reservationModal" onclick="openCreateModal()"><i class="bi bi-plus-lg me-1"></i> Add Reservation</button>
|
|
</div>
|
|
|
|
{{-- DHCP Server Card --}}
|
|
<div class="card border-0 shadow-sm mb-4">
|
|
<div class="card-body">
|
|
<div class="row align-items-center">
|
|
{{-- Server --}}
|
|
<div class="col-md-5">
|
|
<div class="d-flex align-items-center">
|
|
<div class="rounded-circle bg-success bg-opacity-10 d-flex align-items-center justify-content-center me-3" style="width:48px;height:48px;">
|
|
<i class="bi bi-hdd-network text-success fs-4"></i>
|
|
</div>
|
|
<div>
|
|
<div class="fw-semibold" id="dhcpServerHostname">DHCP LAB</div>
|
|
<div class="text-muted small"> <span id="dhcpServerIp">-</span>
|
|
<span class="mx-1">•</span>
|
|
<span id="dhcpServerInterface">-</span></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{{-- Status --}}
|
|
<div class="col-md-3 mt-3 mt-md-0">
|
|
<div class="small text-muted">Status</div>
|
|
<div class="mt-1">
|
|
@if($dhcpStatus['running'])
|
|
<span id="dhcpStatusBadge" class="badge rounded-pill bg-success bg-opacity-10 text-success">
|
|
<span
|
|
class="d-inline-block rounded-circle bg-success me-1"
|
|
style="width:7px;height:7px;"
|
|
></span>
|
|
Running
|
|
</span>
|
|
@else
|
|
<span id="dhcpStatusBadge" class="badge rounded-pill bg-danger bg-opacity-10 text-danger">
|
|
<span
|
|
class="d-inline-block rounded-circle bg-danger me-1"
|
|
style="width:7px;height:7px;"
|
|
></span>
|
|
Stopped
|
|
</span>
|
|
@endif
|
|
</div>
|
|
</div>
|
|
|
|
{{-- Reservations --}}
|
|
<div class="col-md-2 mt-3 mt-md-0">
|
|
<div class="small text-muted">
|
|
Reservations
|
|
</div>
|
|
<div class="fw-semibold mt-1" id="activeClients">
|
|
{{ $dhcpStatus['active_clients'] }}
|
|
<span class="text-muted fw-normal small">
|
|
active
|
|
</span>
|
|
</div>
|
|
</div>
|
|
|
|
|
|
{{-- Last Apply --}}
|
|
<div class="col-md-2 mt-3 mt-md-0">
|
|
<div class="small text-muted">
|
|
Last Applied
|
|
</div>
|
|
<div
|
|
class="fw-semibold mt-1"
|
|
id="lastApplied"
|
|
>
|
|
-
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
|
|
{{-- Filters --}}
|
|
<div class="card border-0 shadow-sm mb-3">
|
|
<div class="card-body py-3">
|
|
<div class="row g-2">
|
|
<div class="col-md-5">
|
|
<div class="input-group">
|
|
<span class="input-group-text bg-white">
|
|
<i class="bi bi-search"></i>
|
|
</span>
|
|
|
|
<input
|
|
type="text"
|
|
class="form-control form-control-sm border-start-0"
|
|
id="searchReservation"
|
|
placeholder="Search hostname, MAC or IP..."
|
|
>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<div class="col-md-3">
|
|
|
|
<select
|
|
class="form-select"
|
|
id="statusFilter"
|
|
>
|
|
<option value="">All Status</option>
|
|
<option value="active">Active</option>
|
|
<option value="disabled">Disabled</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
|
|
{{-- Reservation Table --}}
|
|
<div class="card border-0 shadow-sm">
|
|
<div class="card-body p-0">
|
|
<div class="table-responsive">
|
|
<table
|
|
class="table table-hover align-middle mb-0"
|
|
id="reservationTable">
|
|
<thead class="table-light">
|
|
<tr>
|
|
<th class="px-4">Hostname</th>
|
|
<th>MAC Address</th>
|
|
<th>IP Address</th>
|
|
<th>Status</th>
|
|
<th class="text-end px-4"></th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
@forelse($reservations as $reservation)
|
|
<tr>
|
|
<td class="px-4">
|
|
<div class="fw-semibold">
|
|
{{ $reservation->hostname }}
|
|
</div>
|
|
@if(!empty($reservation->description))
|
|
<div class="text-muted small">
|
|
{{ $reservation->description }}
|
|
</div>
|
|
@endif
|
|
</td>
|
|
|
|
<td>
|
|
<code>{{ $reservation->mac_address }}</code>
|
|
</td>
|
|
|
|
<td>
|
|
<span class="fw-semibold">
|
|
{{ $reservation->ip_address }}
|
|
</span>
|
|
</td>
|
|
|
|
<td>
|
|
@if($reservation->enabled)
|
|
<span class="badge rounded-pill bg-success bg-opacity-10 text-success">
|
|
Active
|
|
</span>
|
|
@else
|
|
<span class="badge rounded-pill bg-secondary bg-opacity-10 text-secondary">
|
|
Disabled
|
|
</span>
|
|
@endif
|
|
</td>
|
|
|
|
<td class="text-end px-4">
|
|
<div class="btn-group">
|
|
<button
|
|
type="button"
|
|
class="btn btn-sm btn-light"
|
|
onclick="editReservation(
|
|
@js($reservation->id),
|
|
@js($reservation->hostname),
|
|
@js($reservation->mac_address),
|
|
@js($reservation->ip_address),
|
|
@js((bool) $reservation->enabled)
|
|
)"
|
|
>
|
|
<i class="bi bi-pencil"></i>
|
|
</button>
|
|
|
|
<button
|
|
type="button"
|
|
class="btn btn-sm btn-light text-danger"
|
|
onclick="deleteReservation(
|
|
@js($reservation->id),
|
|
@js($reservation->hostname)
|
|
)"
|
|
>
|
|
<i class="bi bi-trash"></i>
|
|
</button>
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
@empty
|
|
<tr>
|
|
<td colspan="5" class="text-center py-5 text-muted">
|
|
No DHCP reservations found.
|
|
</td>
|
|
</tr>
|
|
@endforelse
|
|
</tbody>
|
|
|
|
</table>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
|
|
{{-- Footer --}}
|
|
<div class="card-footer bg-white border-0">
|
|
|
|
<div class="d-flex justify-content-between align-items-center">
|
|
|
|
<div class="text-muted small">
|
|
Showing {{ $reservations->count() }} reservations
|
|
</div>
|
|
|
|
<div>
|
|
|
|
@if(method_exists($reservations, 'previousPageUrl') && $reservations->previousPageUrl())
|
|
<a href="{{ $reservations->previousPageUrl() }}"
|
|
class="btn btn-sm btn-outline-secondary">
|
|
Previous
|
|
</a>
|
|
@else
|
|
<button class="btn btn-sm btn-outline-secondary" disabled>
|
|
Previous
|
|
</button>
|
|
@endif
|
|
|
|
@if(method_exists($reservations, 'nextPageUrl') && $reservations->nextPageUrl())
|
|
<a href="{{ $reservations->nextPageUrl() }}"
|
|
class="btn btn-sm btn-outline-secondary">
|
|
Next
|
|
</a>
|
|
@else
|
|
<button class="btn btn-sm btn-outline-secondary" disabled>
|
|
Next
|
|
</button>
|
|
@endif
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
|
|
{{-- Apply Section --}}
|
|
<div class="card border-0 shadow-sm mt-4">
|
|
|
|
<div class="card-body">
|
|
|
|
<div class="row align-items-center">
|
|
|
|
<div class="col">
|
|
|
|
<div class="fw-semibold">
|
|
DHCP Configuration
|
|
</div>
|
|
|
|
<div class="text-muted small mt-1">
|
|
|
|
Changes are stored in the database
|
|
until you apply the configuration.
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<div class="col-auto">
|
|
|
|
<button
|
|
class="btn btn-outline-secondary me-2"
|
|
onclick="testDhcp()"
|
|
>
|
|
<i class="bi bi-shield-check me-1"></i>
|
|
Test Configuration
|
|
</button>
|
|
|
|
<button
|
|
class="btn btn-primary"
|
|
onclick="applyDhcp()"
|
|
>
|
|
<i class="bi bi-arrow-repeat me-1"></i>
|
|
Apply Changes
|
|
</button>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<div class="card border-0 shadow-sm">
|
|
|
|
<div class="card-header bg-white border-0
|
|
d-flex justify-content-between
|
|
align-items-center">
|
|
|
|
<div>
|
|
<div class="fw-semibold">
|
|
Recent DHCP Events
|
|
</div>
|
|
|
|
<div class="text-muted small">
|
|
Latest activity from DHCP server
|
|
</div>
|
|
</div>
|
|
|
|
<button
|
|
type="button"
|
|
class="btn btn-sm btn-light"
|
|
onclick="loadDhcpLogs()"
|
|
>
|
|
<i class="bi bi-arrow-clockwise"></i>
|
|
Refresh
|
|
</button>
|
|
|
|
</div>
|
|
|
|
<div class="card-body p-0">
|
|
|
|
<div
|
|
id="dhcpLogs"
|
|
class="font-monospace small"
|
|
style="
|
|
max-height: 320px;
|
|
overflow-y: auto;
|
|
"
|
|
>
|
|
|
|
<div class="text-muted p-4">
|
|
Loading DHCP events...
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
|
|
{{-- Add / Edit Modal --}}
|
|
<div
|
|
class="modal fade"
|
|
id="reservationModal"
|
|
tabindex="-1"
|
|
>
|
|
|
|
<div class="modal-dialog modal-dialog-centered">
|
|
|
|
<div class="modal-content border-0 shadow">
|
|
|
|
<div class="modal-header">
|
|
|
|
<div>
|
|
|
|
<h5
|
|
class="modal-title mb-1"
|
|
id="reservationModalTitle"
|
|
>
|
|
Add DHCP Reservation
|
|
</h5>
|
|
|
|
<div class="text-muted small">
|
|
Assign a fixed IP address to a DHCP client
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<button
|
|
type="button"
|
|
class="btn-close"
|
|
data-bs-dismiss="modal"
|
|
></button>
|
|
|
|
</div>
|
|
|
|
|
|
<form id="reservationForm">
|
|
|
|
<div class="modal-body">
|
|
|
|
<input
|
|
type="hidden"
|
|
id="reservationId"
|
|
value=""
|
|
>
|
|
|
|
|
|
{{-- Hostname --}}
|
|
<div class="mb-3">
|
|
|
|
<label class="form-label fw-semibold">
|
|
Hostname
|
|
</label>
|
|
|
|
<input
|
|
type="text"
|
|
class="form-control"
|
|
id="hostname"
|
|
placeholder="e.g. kasir-01"
|
|
required
|
|
>
|
|
|
|
<div class="form-text">
|
|
Only letters, numbers, dash and underscore.
|
|
</div>
|
|
|
|
</div>
|
|
|
|
|
|
{{-- MAC --}}
|
|
<div class="mb-3">
|
|
|
|
<label class="form-label fw-semibold">
|
|
MAC Address
|
|
</label>
|
|
|
|
<input
|
|
type="text"
|
|
class="form-control font-monospace"
|
|
id="macAddress"
|
|
placeholder="AA:BB:CC:DD:EE:FF"
|
|
maxlength="17"
|
|
required
|
|
>
|
|
|
|
<div
|
|
class="invalid-feedback"
|
|
id="macError"
|
|
>
|
|
Invalid MAC address.
|
|
</div>
|
|
|
|
</div>
|
|
|
|
|
|
{{-- IP --}}
|
|
<div class="mb-3">
|
|
|
|
<label class="form-label fw-semibold">
|
|
IP Address
|
|
</label>
|
|
|
|
<input
|
|
type="text"
|
|
class="form-control font-monospace"
|
|
id="ipAddress"
|
|
placeholder="192.168.10.50"
|
|
required
|
|
>
|
|
|
|
<div
|
|
class="invalid-feedback"
|
|
id="ipError"
|
|
>
|
|
Invalid IP address.
|
|
</div>
|
|
|
|
</div>
|
|
|
|
|
|
{{-- Enabled --}}
|
|
<div
|
|
class="form-check form-switch"
|
|
>
|
|
|
|
<input
|
|
class="form-check-input"
|
|
type="checkbox"
|
|
id="enabled"
|
|
checked
|
|
>
|
|
|
|
<label
|
|
class="form-check-label"
|
|
for="enabled"
|
|
>
|
|
Enable reservation
|
|
</label>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
|
|
<div class="modal-footer">
|
|
|
|
<button
|
|
type="button"
|
|
class="btn btn-light"
|
|
data-bs-dismiss="modal"
|
|
>
|
|
Cancel
|
|
</button>
|
|
|
|
<button
|
|
type="submit"
|
|
class="btn btn-primary"
|
|
>
|
|
<i class="bi bi-check-lg me-1"></i>
|
|
Save Reservation
|
|
</button>
|
|
|
|
</div>
|
|
|
|
</form>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
|
|
{{-- Apply Confirmation --}}
|
|
<div
|
|
class="modal fade"
|
|
id="applyModal"
|
|
tabindex="-1"
|
|
>
|
|
|
|
<div class="modal-dialog modal-dialog-centered">
|
|
|
|
<div class="modal-content border-0 shadow">
|
|
|
|
<div class="modal-body text-center p-4">
|
|
|
|
<div
|
|
class="rounded-circle bg-warning bg-opacity-10
|
|
d-flex align-items-center justify-content-center
|
|
mx-auto mb-3"
|
|
style="width:64px;height:64px;"
|
|
>
|
|
<i class="bi bi-exclamation-triangle text-warning fs-3"></i>
|
|
</div>
|
|
|
|
<h5>
|
|
Apply DHCP Configuration?
|
|
</h5>
|
|
|
|
<p class="text-muted mb-4">
|
|
|
|
The DHCP configuration will be validated
|
|
before the DHCP service is restarted.
|
|
|
|
</p>
|
|
|
|
<button
|
|
class="btn btn-light me-2"
|
|
data-bs-dismiss="modal"
|
|
>
|
|
Cancel
|
|
</button>
|
|
|
|
<button
|
|
class="btn btn-primary"
|
|
onclick="confirmApply()"
|
|
>
|
|
Apply Configuration
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
@endsection
|
|
|
|
@section('javascript')
|
|
<script>
|
|
window.dhcpConfig = {
|
|
reservationStoreUrl: @json(route('dhcp.reservations.store')),
|
|
reservationBaseUrl: @json(url('/dhcp/reservations')),
|
|
applyUrl: @json(route('dhcp.apply')),
|
|
statusUrl: @json(route('dhcp.status')),
|
|
logsUrl: @json(route('dhcp.logs')),
|
|
};
|
|
</script>
|
|
<script src="{{ asset('vendor/sweetalert2/dist/sweetalert2.min.js') }}"></script>
|
|
<script src="{{ asset('js/setup/dhcp.js') }}"></script>
|
|
|
|
@endsection
|