This commit is contained in:
Mariano Z. 2025-05-03 17:36:17 -03:00
commit 682f25edcd
Signed by: marianozunino
GPG key ID: 4C73BAD25156DACE
19 changed files with 1907 additions and 0 deletions

512
assets/js/app.js Normal file
View file

@ -0,0 +1,512 @@
// 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();
}
});
});