batman
This commit is contained in:
commit
682f25edcd
19 changed files with 1907 additions and 0 deletions
512
assets/js/app.js
Normal file
512
assets/js/app.js
Normal 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();
|
||||
}
|
||||
});
|
||||
});
|
Loading…
Add table
Add a link
Reference in a new issue