383 lines
12 KiB
JavaScript
383 lines
12 KiB
JavaScript
let editing = false;
|
|
/** Create */
|
|
function openCreateModal() {
|
|
editing = false;
|
|
|
|
document.getElementById("reservationModalTitle").innerText = "Add DHCP Reservation";
|
|
|
|
document.getElementById("reservationForm").reset();
|
|
document.getElementById("reservationId").value = "";
|
|
document.getElementById("enabled").checked = true;
|
|
|
|
clearValidation();
|
|
}
|
|
|
|
/*** Edit*/
|
|
function editReservation(hostname, mac, ip, enabled) {
|
|
editing = true;
|
|
document.getElementById("reservationModalTitle").innerText = "Edit DHCP Reservation";
|
|
document.getElementById("hostname").value = hostname;
|
|
document.getElementById("macAddress").value = mac;
|
|
document.getElementById("ipAddress").value = ip;
|
|
document.getElementById("enabled").checked = enabled;
|
|
const modal = new bootstrap.Modal(document.getElementById("reservationModal"));
|
|
modal.show();
|
|
}
|
|
|
|
/*** MAC formatting */
|
|
document.getElementById("macAddress").addEventListener("input", function () {
|
|
let value = this.value.replace(/[^a-fA-F0-9]/g, "").toUpperCase();
|
|
|
|
let pairs = value.match(/.{1,2}/g);
|
|
|
|
this.value = pairs ? pairs.join(":").substring(0, 17) : "";
|
|
});
|
|
function clearValidation() {
|
|
document.getElementById("macAddress").classList.remove("is-invalid");
|
|
document.getElementById("ipAddress").classList.remove("is-invalid");
|
|
document.getElementById("macError").innerText = "Invalid MAC address.";
|
|
document.getElementById("ipError").innerText = "Invalid IP address.";
|
|
}
|
|
|
|
document.getElementById("reservationForm").addEventListener("submit", function (event) {
|
|
event.preventDefault();
|
|
|
|
clearValidation();
|
|
|
|
const id = document.getElementById("reservationId").value;
|
|
|
|
const payload = {
|
|
hostname: document.getElementById("hostname").value.trim(),
|
|
|
|
mac_address: document.getElementById("macAddress").value.trim(),
|
|
|
|
ip_address: document.getElementById("ipAddress").value.trim(),
|
|
|
|
enabled: document.getElementById("enabled").checked,
|
|
};
|
|
|
|
const macValid = /^([0-9A-F]{2}:){5}[0-9A-F]{2}$/i.test(payload.mac_address);
|
|
|
|
const ipValid = /^(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}$/.test(
|
|
payload.ip_address,
|
|
);
|
|
|
|
if (!macValid) {
|
|
document.getElementById("macAddress").classList.add("is-invalid");
|
|
|
|
return;
|
|
}
|
|
|
|
if (!ipValid) {
|
|
document.getElementById("ipAddress").classList.add("is-invalid");
|
|
|
|
return;
|
|
}
|
|
|
|
const url = id ? dhcpConfig.reservationBaseUrl + "/" + id : dhcpConfig.reservationStoreUrl;
|
|
|
|
const method = id ? "PUT" : "POST";
|
|
|
|
const submitButton = this.querySelector('button[type="submit"]');
|
|
|
|
if (submitButton) {
|
|
submitButton.disabled = true;
|
|
submitButton.innerHTML = '<span class="spinner-border spinner-border-sm me-1"></span>' + "Saving...";
|
|
}
|
|
|
|
fetch(url, {
|
|
method: method,
|
|
headers: {
|
|
"X-CSRF-TOKEN": document.querySelector('meta[name="csrf-token"]').getAttribute("content"),
|
|
|
|
Accept: "application/json",
|
|
"Content-Type": "application/json",
|
|
},
|
|
|
|
body: JSON.stringify(payload),
|
|
})
|
|
.then(async (response) => {
|
|
const data = await response.json();
|
|
|
|
if (!response.ok) {
|
|
throw data;
|
|
}
|
|
|
|
return data;
|
|
})
|
|
.then((data) => {
|
|
if (!data.success) {
|
|
throw data;
|
|
}
|
|
|
|
const modal = bootstrap.Modal.getInstance(document.getElementById("reservationModal"));
|
|
|
|
if (modal) {
|
|
modal.hide();
|
|
}
|
|
|
|
window.location.reload();
|
|
})
|
|
.catch((error) => {
|
|
console.error("Reservation save error:", error);
|
|
|
|
if (error.errors && error.errors.mac_address) {
|
|
document.getElementById("macAddress").classList.add("is-invalid");
|
|
|
|
document.getElementById("macError").innerText = error.errors.mac_address[0];
|
|
}
|
|
|
|
if (error.errors && error.errors.ip_address) {
|
|
document.getElementById("ipAddress").classList.add("is-invalid");
|
|
|
|
document.getElementById("ipError").innerText = error.errors.ip_address[0];
|
|
}
|
|
|
|
Swal.fire({icon: 'error', title: 'Gagal menyimpan', text: error.message || "Failed to save reservation."});
|
|
})
|
|
.finally(() => {
|
|
if (submitButton) {
|
|
submitButton.disabled = false;
|
|
submitButton.innerHTML = '<i class="bi bi-check-lg me-1"></i>' + "Save Reservation";
|
|
}
|
|
});
|
|
});
|
|
|
|
/*** Search*/
|
|
document.getElementById("searchReservation").addEventListener("input", function () {
|
|
const keyword = this.value.toLowerCase();
|
|
document.querySelectorAll("#reservationTable tbody tr").forEach(function (row) {
|
|
row.style.display = row.innerText.toLowerCase().includes(keyword) ? "" : "none";
|
|
});
|
|
});
|
|
|
|
/*** Delete*/
|
|
function deleteReservation(hostname) {
|
|
Swal.fire({title: 'Hapus reservasi "' + hostname + '"?', icon: 'warning', showCancelButton: true, confirmButtonText: 'Ya, hapus', cancelButtonText: 'Batal', confirmButtonColor: '#dc3545'})
|
|
.then((res) => {
|
|
if (!res.isConfirmed) return;
|
|
Swal.fire({icon: 'info', title: 'Mode demo', text: 'Reservation "' + hostname + '" deleted.'});
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Test DHCP
|
|
*/
|
|
function testDhcp() {
|
|
Swal.fire({icon: 'info', title: 'Mode demo', text: "DHCP configuration is valid."});
|
|
}
|
|
|
|
/**
|
|
* Apply
|
|
*/
|
|
function applyDhcp() {
|
|
const modal = new bootstrap.Modal(document.getElementById("applyModal"));
|
|
modal.show();
|
|
}
|
|
|
|
/*** Confirm Apply */
|
|
function confirmApply() {
|
|
const modalElement = document.getElementById("applyModal");
|
|
|
|
const modal = bootstrap.Modal.getInstance(modalElement);
|
|
|
|
if (modal) {
|
|
modal.hide();
|
|
}
|
|
|
|
const button = document.querySelector('#applyModal [onclick="confirmApply()"]');
|
|
|
|
if (button) {
|
|
button.disabled = true;
|
|
button.innerHTML = '<span class="spinner-border spinner-border-sm me-1"></span>' + "Applying...";
|
|
}
|
|
|
|
fetch(dhcpConfig.applyUrl, {
|
|
method: "POST",
|
|
headers: {
|
|
"X-CSRF-TOKEN": document.querySelector('meta[name="csrf-token"]').getAttribute("content"),
|
|
Accept: "application/json",
|
|
},
|
|
})
|
|
.then(async (response) => {
|
|
const data = await response.json();
|
|
|
|
if (!response.ok) {
|
|
throw data;
|
|
}
|
|
|
|
return data;
|
|
})
|
|
.then((data) => {
|
|
if (!data.success) {
|
|
throw data;
|
|
}
|
|
|
|
Swal.fire({icon: 'success', title: 'Berhasil', text: data.message || "DHCP configuration applied successfully."});
|
|
|
|
refreshDhcpStatus();
|
|
window.location.reload();
|
|
})
|
|
.catch((error) => {
|
|
console.error("DHCP apply error:", error);
|
|
|
|
Swal.fire({icon: 'error', title: 'Gagal apply', text: error.message || "Failed to apply DHCP configuration."});
|
|
})
|
|
.finally(() => {
|
|
if (button) {
|
|
button.disabled = false;
|
|
button.innerHTML = "Apply Configuration";
|
|
}
|
|
});
|
|
}
|
|
|
|
function refreshDhcpStatus() {
|
|
fetch(dhcpConfig.statusUrl)
|
|
.then(async (response) => {
|
|
if (!response.ok) {
|
|
throw new Error("HTTP " + response.status);
|
|
}
|
|
|
|
return response.json();
|
|
})
|
|
.then((data) => {
|
|
document.getElementById("dhcpServerHostname").innerText = data.hostname ?? "DHCP Server";
|
|
|
|
const badge = document.getElementById("dhcpStatusBadge");
|
|
|
|
const clients = document.getElementById("activeClients");
|
|
|
|
const lastApplied = document.getElementById("lastApplied");
|
|
const serverIp = document.getElementById("dhcpServerIp");
|
|
|
|
const serverInterface = document.getElementById("dhcpServerInterface");
|
|
|
|
serverIp.innerText = data.ip ?? "-";
|
|
|
|
serverInterface.innerText = data.interface ?? "-";
|
|
|
|
// STATUS
|
|
if (data.running) {
|
|
badge.innerHTML = `
|
|
<span 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 {
|
|
badge.innerHTML = `
|
|
<span 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>
|
|
`;
|
|
}
|
|
|
|
// ACTIVE CLIENTS
|
|
clients.innerHTML =
|
|
(data.reservations_count ?? data.active_clients ?? 0) +
|
|
' <span class="text-muted fw-normal small">registered</span>';
|
|
|
|
// LAST APPLIED
|
|
if (data.last_applied) {
|
|
const date = new Date(data.last_applied.replace(" ", "T"));
|
|
|
|
lastApplied.innerText = date.toLocaleString("id-ID", {
|
|
day: "2-digit",
|
|
month: "short",
|
|
hour: "2-digit",
|
|
minute: "2-digit",
|
|
});
|
|
} else {
|
|
lastApplied.innerText = "Never";
|
|
}
|
|
})
|
|
.catch((error) => {
|
|
console.error("DHCP status error:", error);
|
|
});
|
|
}
|
|
|
|
function loadDhcpLogs() {
|
|
const container = document.getElementById("dhcpLogs");
|
|
|
|
container.innerHTML = `
|
|
<div class="text-muted p-4">
|
|
Loading...
|
|
</div>
|
|
`;
|
|
|
|
fetch(dhcpConfig.logsUrl)
|
|
.then(async (response) => {
|
|
if (!response.ok) {
|
|
throw new Error("HTTP " + response.status);
|
|
}
|
|
|
|
return response.json();
|
|
})
|
|
|
|
.then((data) => {
|
|
if (!data.logs || !data.logs.length) {
|
|
container.innerHTML = `
|
|
<div class="text-muted p-4">
|
|
No DHCP events found.
|
|
</div>
|
|
`;
|
|
|
|
return;
|
|
}
|
|
|
|
container.innerHTML = data.logs
|
|
.map((log) => {
|
|
const escaped = escapeHtml(log);
|
|
|
|
let className = "text-muted";
|
|
|
|
if (log.includes("DHCPACK")) {
|
|
className = "text-success";
|
|
} else if (log.includes("DHCPNAK")) {
|
|
className = "text-danger";
|
|
} else if (log.includes("DHCPREQUEST")) {
|
|
className = "text-primary";
|
|
} else if (log.includes("DHCPDISCOVER")) {
|
|
className = "text-warning";
|
|
}
|
|
|
|
return `
|
|
<div class="px-4 py-2 border-bottom
|
|
${className}">
|
|
${escaped}
|
|
</div>
|
|
`;
|
|
})
|
|
.join("");
|
|
})
|
|
|
|
.catch((error) => {
|
|
console.error("DHCP logs error:", error);
|
|
|
|
container.innerHTML = `
|
|
<div class="text-danger p-4">
|
|
Failed to load DHCP logs.
|
|
</div>
|
|
`;
|
|
});
|
|
}
|
|
|
|
function escapeHtml(value) {
|
|
const div = document.createElement("div");
|
|
|
|
div.textContent = value;
|
|
|
|
return div.innerHTML;
|
|
}
|
|
|
|
refreshDhcpStatus();
|
|
loadDhcpLogs();
|
|
setInterval(refreshDhcpStatus, 5000, loadDhcpLogs, 5000);
|