512 lines
16 KiB
JavaScript
512 lines
16 KiB
JavaScript
// Global variables
|
|
let isConfigured = false;
|
|
let currentIP = "";
|
|
let domainName = "mz.uy";
|
|
|
|
// Helper functions
|
|
function showToast(message, type = "success") {
|
|
const toastContainer = document.querySelector(".toast-container");
|
|
const toast = document.createElement("div");
|
|
toast.className = `toast align-items-center text-white bg-${type}`;
|
|
toast.setAttribute("role", "alert");
|
|
toast.setAttribute("aria-live", "assertive");
|
|
toast.setAttribute("aria-atomic", "true");
|
|
|
|
toast.innerHTML = `
|
|
<div class="d-flex">
|
|
<div class="toast-body">
|
|
${message}
|
|
</div>
|
|
<button type="button" class="btn-close btn-close-white me-2 m-auto" data-bs-dismiss="toast" aria-label="Close"></button>
|
|
</div>
|
|
`;
|
|
|
|
toastContainer.appendChild(toast);
|
|
const bsToast = new bootstrap.Toast(toast);
|
|
bsToast.show();
|
|
|
|
// Remove the toast after it's hidden
|
|
toast.addEventListener("hidden.bs.toast", () => {
|
|
toast.remove();
|
|
});
|
|
}
|
|
|
|
// Initialize the application
|
|
async function initApp() {
|
|
// Show loading indicator
|
|
document.getElementById("loading").style.display = "block";
|
|
document.getElementById("config-warning").style.display = "none";
|
|
document.getElementById("config-status").style.display = "none";
|
|
document.getElementById("dns-records-section").style.display = "none";
|
|
|
|
// Load configuration
|
|
await loadConfig();
|
|
|
|
// Load current IP
|
|
await loadCurrentIP();
|
|
|
|
// Load update frequencies for the dropdown
|
|
await loadUpdateFrequencies();
|
|
|
|
// If configured, load DNS records
|
|
if (isConfigured) {
|
|
await loadDNSRecords();
|
|
}
|
|
|
|
// Hide loading indicator
|
|
document.getElementById("loading").style.display = "none";
|
|
}
|
|
|
|
// Load configuration
|
|
async function loadConfig() {
|
|
try {
|
|
const response = await fetch("/api/config");
|
|
const data = await response.json();
|
|
|
|
isConfigured = data.is_configured;
|
|
domainName = data.domain;
|
|
|
|
if (isConfigured) {
|
|
document.getElementById("config-warning").style.display = "none";
|
|
document.getElementById("config-status").style.display = "block";
|
|
document.getElementById("dns-records-section").style.display = "block";
|
|
|
|
document.getElementById("zone-id").textContent = data.zone_id;
|
|
document.getElementById("domain-name").textContent = data.domain;
|
|
document.getElementById("domain-suffix").textContent = "." + data.domain;
|
|
document.getElementById("domain-input").value = data.domain;
|
|
document.getElementById("zone-id-input").value = data.zone_id;
|
|
|
|
// Set the update schedule display
|
|
let scheduleDisplay = "Manual updates only";
|
|
if (data.update_period) {
|
|
switch (data.update_period) {
|
|
case "*/5 * * * *":
|
|
scheduleDisplay = "Every 5 minutes";
|
|
break;
|
|
case "*/30 * * * *":
|
|
scheduleDisplay = "Every 30 minutes";
|
|
break;
|
|
case "0 * * * *":
|
|
scheduleDisplay = "Hourly";
|
|
break;
|
|
case "0 */6 * * *":
|
|
scheduleDisplay = "Every 6 hours";
|
|
break;
|
|
case "0 0 * * *":
|
|
scheduleDisplay = "Daily";
|
|
break;
|
|
default:
|
|
scheduleDisplay = data.update_period;
|
|
}
|
|
}
|
|
document.getElementById("update-schedule").textContent = scheduleDisplay;
|
|
} else {
|
|
document.getElementById("config-warning").style.display = "block";
|
|
document.getElementById("config-status").style.display = "none";
|
|
document.getElementById("dns-records-section").style.display = "none";
|
|
}
|
|
} catch (error) {
|
|
console.error("Failed to load configuration:", error);
|
|
showToast("Failed to load configuration: " + error.message, "danger");
|
|
}
|
|
}
|
|
|
|
// Load current IP
|
|
async function loadCurrentIP() {
|
|
try {
|
|
const response = await fetch("/api/current-ip");
|
|
const data = await response.json();
|
|
|
|
currentIP = data.ip;
|
|
document.getElementById("current-ip").textContent = currentIP;
|
|
} catch (error) {
|
|
console.error("Failed to load current IP:", error);
|
|
document.getElementById("current-ip").textContent = "Failed to load";
|
|
}
|
|
}
|
|
|
|
// Load update frequencies
|
|
async function loadUpdateFrequencies() {
|
|
try {
|
|
const response = await fetch("/api/update-frequencies");
|
|
const frequencies = await response.json();
|
|
|
|
const select = document.getElementById("update-period");
|
|
select.innerHTML = "";
|
|
|
|
frequencies.forEach((freq) => {
|
|
const option = document.createElement("option");
|
|
option.value = freq.value;
|
|
option.textContent = freq.label;
|
|
select.appendChild(option);
|
|
});
|
|
} catch (error) {
|
|
console.error("Failed to load update frequencies:", error);
|
|
}
|
|
}
|
|
|
|
// Load DNS records
|
|
async function loadDNSRecords() {
|
|
try {
|
|
const response = await fetch("/api/records");
|
|
if (!response.ok) {
|
|
throw new Error("Failed to fetch DNS records");
|
|
}
|
|
|
|
const records = await response.json();
|
|
const tbody = document.getElementById("dns-records");
|
|
tbody.innerHTML = "";
|
|
|
|
if (records.length === 0) {
|
|
const tr = document.createElement("tr");
|
|
tr.innerHTML =
|
|
'<td colspan="6" class="text-center">No DNS records found</td>';
|
|
tbody.appendChild(tr);
|
|
return;
|
|
}
|
|
|
|
records.forEach((record) => {
|
|
const tr = document.createElement("tr");
|
|
|
|
// Highlight records that match the current IP
|
|
const isCurrentIP = record.type === "A" && record.content === currentIP;
|
|
const ipBadge = isCurrentIP
|
|
? '<span class="badge bg-success update-badge">Current IP</span>'
|
|
: record.type === "A"
|
|
? '<span class="badge bg-warning update-badge">Outdated IP</span>'
|
|
: "";
|
|
|
|
tr.innerHTML = `
|
|
<td>${record.type}</td>
|
|
<td>${record.name}</td>
|
|
<td>${record.content} ${ipBadge}</td>
|
|
<td>${record.ttl === 1 ? "Auto" : record.ttl + "s"}</td>
|
|
<td>${record.proxied ? '<i class="bi bi-check-lg text-success"></i>' : ""}</td>
|
|
<td>
|
|
<button class="btn btn-sm btn-outline-primary me-1 edit-record" data-id="${record.id}">
|
|
<i class="bi bi-pencil"></i>
|
|
</button>
|
|
<button class="btn btn-sm btn-outline-danger delete-record" data-id="${record.id}" data-name="${record.name}">
|
|
<i class="bi bi-trash"></i>
|
|
</button>
|
|
</td>
|
|
`;
|
|
|
|
tbody.appendChild(tr);
|
|
});
|
|
|
|
// Add event listeners for edit and delete buttons
|
|
document.querySelectorAll(".edit-record").forEach((button) => {
|
|
button.addEventListener("click", () => editRecord(button.dataset.id));
|
|
});
|
|
|
|
document.querySelectorAll(".delete-record").forEach((button) => {
|
|
button.addEventListener("click", () =>
|
|
deleteRecord(button.dataset.id, button.dataset.name),
|
|
);
|
|
});
|
|
} catch (error) {
|
|
console.error("Failed to load DNS records:", error);
|
|
showToast("Failed to load DNS records: " + error.message, "danger");
|
|
}
|
|
}
|
|
|
|
// Edit a DNS record
|
|
async function editRecord(id) {
|
|
try {
|
|
const response = await fetch("/api/records");
|
|
const records = await response.json();
|
|
|
|
const record = records.find((r) => r.id === id);
|
|
if (!record) {
|
|
showToast("Record not found", "danger");
|
|
return;
|
|
}
|
|
|
|
// Open the modal
|
|
const modal = new bootstrap.Modal(document.getElementById("recordModal"));
|
|
modal.show();
|
|
|
|
// Update modal title
|
|
document.getElementById("recordModalLabel").textContent = "Edit DNS Record";
|
|
|
|
// Fill the form
|
|
document.getElementById("record-id").value = record.id;
|
|
|
|
// Set the subdomain name without the domain suffix
|
|
let name = record.name;
|
|
if (name === domainName) {
|
|
name = "@";
|
|
} else if (name.endsWith("." + domainName)) {
|
|
name = name.substring(0, name.length - domainName.length - 1);
|
|
}
|
|
document.getElementById("record-name").value = name;
|
|
|
|
document.getElementById("record-type").value = record.type;
|
|
document.getElementById("record-content").value = record.content;
|
|
document.getElementById("record-ttl").value = record.ttl;
|
|
document.getElementById("record-proxied").checked = record.proxied;
|
|
document.getElementById("use-my-ip").checked = false;
|
|
|
|
// Show/hide the "Use my current IP" option based on record type
|
|
toggleMyIPOption();
|
|
} catch (error) {
|
|
console.error("Failed to load record:", error);
|
|
showToast("Failed to load record: " + error.message, "danger");
|
|
}
|
|
}
|
|
|
|
// Delete a DNS record
|
|
async function deleteRecord(id, name) {
|
|
if (!confirm(`Are you sure you want to delete the record for "${name}"?`)) {
|
|
return;
|
|
}
|
|
|
|
try {
|
|
const response = await fetch(`/api/records/${id}`, {
|
|
method: "DELETE",
|
|
});
|
|
|
|
if (!response.ok) {
|
|
const error = await response.json();
|
|
throw new Error(error.error || "Failed to delete record");
|
|
}
|
|
|
|
showToast(`Record for "${name}" deleted successfully`);
|
|
await loadDNSRecords();
|
|
} catch (error) {
|
|
console.error("Failed to delete record:", error);
|
|
showToast("Failed to delete record: " + error.message, "danger");
|
|
}
|
|
}
|
|
|
|
// Toggle the "Use my current IP" option based on record type
|
|
function toggleMyIPOption() {
|
|
const recordType = document.getElementById("record-type").value;
|
|
const useMyIPGroup = document.getElementById("use-my-ip-group");
|
|
const contentGroup = document.getElementById("content-group");
|
|
|
|
if (recordType === "A") {
|
|
useMyIPGroup.style.display = "block";
|
|
|
|
// If the checkbox is checked, hide the content field
|
|
const useMyIP = document.getElementById("use-my-ip").checked;
|
|
contentGroup.style.display = useMyIP ? "none" : "block";
|
|
} else {
|
|
useMyIPGroup.style.display = "none";
|
|
contentGroup.style.display = "block";
|
|
}
|
|
}
|
|
|
|
// Event listeners
|
|
document.addEventListener("DOMContentLoaded", function () {
|
|
// Initialize the application
|
|
initApp();
|
|
|
|
// Refresh IP button
|
|
document
|
|
.getElementById("refresh-ip")
|
|
.addEventListener("click", async function () {
|
|
await loadCurrentIP();
|
|
showToast("Current IP refreshed");
|
|
});
|
|
|
|
// Save configuration button
|
|
document
|
|
.getElementById("save-config")
|
|
.addEventListener("click", async function () {
|
|
const apiToken = document.getElementById("api-token").value;
|
|
const zoneId = document.getElementById("zone-id-input").value;
|
|
const domain = document.getElementById("domain-input").value;
|
|
const updatePeriod = document.getElementById("update-period").value;
|
|
|
|
if (!apiToken || !zoneId || !domain) {
|
|
showToast("Please fill all required fields", "danger");
|
|
return;
|
|
}
|
|
|
|
try {
|
|
const response = await fetch("/api/config", {
|
|
method: "POST",
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
},
|
|
body: JSON.stringify({
|
|
api_token: apiToken,
|
|
zone_id: zoneId,
|
|
domain: domain,
|
|
update_period: updatePeriod,
|
|
}),
|
|
});
|
|
|
|
if (!response.ok) {
|
|
const error = await response.json();
|
|
throw new Error(error.error || "Failed to save configuration");
|
|
}
|
|
|
|
// Hide the modal
|
|
const modal = bootstrap.Modal.getInstance(
|
|
document.getElementById("configModal"),
|
|
);
|
|
modal.hide();
|
|
|
|
showToast("Configuration saved successfully");
|
|
|
|
// Reload the app
|
|
initApp();
|
|
} catch (error) {
|
|
console.error("Failed to save configuration:", error);
|
|
showToast("Failed to save configuration: " + error.message, "danger");
|
|
}
|
|
});
|
|
|
|
// Record type change event
|
|
document
|
|
.getElementById("record-type")
|
|
.addEventListener("change", toggleMyIPOption);
|
|
|
|
// Use my IP checkbox change event
|
|
document.getElementById("use-my-ip").addEventListener("change", function () {
|
|
const contentGroup = document.getElementById("content-group");
|
|
contentGroup.style.display = this.checked ? "none" : "block";
|
|
|
|
if (this.checked) {
|
|
document.getElementById("record-content").value = currentIP;
|
|
}
|
|
});
|
|
|
|
// Save record button
|
|
document
|
|
.getElementById("save-record")
|
|
.addEventListener("click", async function () {
|
|
const id = document.getElementById("record-id").value;
|
|
let name = document.getElementById("record-name").value;
|
|
const type = document.getElementById("record-type").value;
|
|
const content = document.getElementById("record-content").value;
|
|
const ttl = parseInt(document.getElementById("record-ttl").value);
|
|
const proxied = document.getElementById("record-proxied").checked;
|
|
const useMyIP = document.getElementById("use-my-ip").checked;
|
|
|
|
// Validate the form
|
|
if (!name) {
|
|
showToast("Name is required", "danger");
|
|
return;
|
|
}
|
|
|
|
if (!useMyIP && !content) {
|
|
showToast("Content is required", "danger");
|
|
return;
|
|
}
|
|
|
|
// Prepare the record data
|
|
const recordData = {
|
|
name: name,
|
|
type: type,
|
|
content: useMyIP ? "" : content,
|
|
ttl: ttl,
|
|
proxied: proxied,
|
|
use_my_ip: useMyIP,
|
|
};
|
|
|
|
try {
|
|
let response;
|
|
|
|
if (id) {
|
|
// Update existing record
|
|
response = await fetch(`/api/records/${id}`, {
|
|
method: "PUT",
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
},
|
|
body: JSON.stringify(recordData),
|
|
});
|
|
} else {
|
|
// Create new record
|
|
response = await fetch("/api/records", {
|
|
method: "POST",
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
},
|
|
body: JSON.stringify(recordData),
|
|
});
|
|
}
|
|
|
|
if (!response.ok) {
|
|
const error = await response.json();
|
|
throw new Error(error.error || "Failed to save record");
|
|
}
|
|
|
|
// Hide the modal
|
|
const modal = bootstrap.Modal.getInstance(
|
|
document.getElementById("recordModal"),
|
|
);
|
|
modal.hide();
|
|
|
|
showToast(
|
|
id ? "Record updated successfully" : "Record created successfully",
|
|
);
|
|
|
|
// Reset the form
|
|
document.getElementById("record-form").reset();
|
|
document.getElementById("record-id").value = "";
|
|
|
|
// Reload DNS records
|
|
await loadDNSRecords();
|
|
} catch (error) {
|
|
console.error("Failed to save record:", error);
|
|
showToast("Failed to save record: " + error.message, "danger");
|
|
}
|
|
});
|
|
|
|
// Update all records button
|
|
document
|
|
.getElementById("update-all-records")
|
|
.addEventListener("click", async function () {
|
|
if (
|
|
!confirm(
|
|
"Are you sure you want to update all A records to your current IP?",
|
|
)
|
|
) {
|
|
return;
|
|
}
|
|
|
|
try {
|
|
const response = await fetch("/api/update-all", {
|
|
method: "POST",
|
|
});
|
|
|
|
if (!response.ok) {
|
|
const error = await response.json();
|
|
throw new Error(error.error || "Failed to update records");
|
|
}
|
|
|
|
showToast("All A records updated to current IP");
|
|
|
|
// Reload DNS records
|
|
await loadDNSRecords();
|
|
} catch (error) {
|
|
console.error("Failed to update records:", error);
|
|
showToast("Failed to update records: " + error.message, "danger");
|
|
}
|
|
});
|
|
|
|
// Reset record form when opening the modal
|
|
document
|
|
.getElementById("recordModal")
|
|
.addEventListener("show.bs.modal", function (event) {
|
|
const button = event.relatedTarget;
|
|
|
|
// If opening from the "Add Record" button, reset the form
|
|
if (button && button.textContent.trim().includes("Add Record")) {
|
|
document.getElementById("recordModalLabel").textContent =
|
|
"Add DNS Record";
|
|
document.getElementById("record-form").reset();
|
|
document.getElementById("record-id").value = "";
|
|
document.getElementById("record-type").value = "A";
|
|
|
|
// Show/hide the "Use my current IP" option based on record type
|
|
toggleMyIPOption();
|
|
}
|
|
});
|
|
});
|