Kumpulan perubahan yang udah numpuk di working tree: refactor Setup (Gate/Vehicle/Member), rapihin memberin & stiker, service parking baru (ParkingUtilsServices, NetworkService), model/migration lot-parking & DHCP reservation, dan fitur baru Intercom (controller/model/migration/ view). ReportTransaksiController versi lokal ini WIP (~90%, masih didebug) - versi yang bener ada di server, jadi dikeluarin dari git (ditambahin ke .gitignore) biar gak numpuk nindihin punya server pas git pull di sana. Sekalian keluarin dump mentah tabel softseting (1.8MB, nyasar ke-commit), file .bak, dan arsip ad-hoc (api.tgz, c.zip) yang emang bukan source code. Co-Authored-By: Claude Sonnet 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01EAJc2mrpj2cSTe4oowLPzQ
604 lines
19 KiB
PHP
604 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('js/setup/dhcp.js') }}"></script>
|
|
|
|
@endsection
|