fix: code review fixes
This commit is contained in:
+254
@@ -1,5 +1,204 @@
|
||||
// main.js
|
||||
|
||||
// --- 2FA Implementation ---
|
||||
let twoFactorVerified = false;
|
||||
|
||||
// Check 2FA status on page load
|
||||
async function check2FAStatus() {
|
||||
try {
|
||||
const res = await fetch("/2fa/status");
|
||||
if (res.ok) {
|
||||
const data = await res.json();
|
||||
twoFactorVerified = data.verified;
|
||||
if (!twoFactorVerified) {
|
||||
show2FAModal();
|
||||
}
|
||||
} else {
|
||||
show2FAModal();
|
||||
}
|
||||
} catch (err) {
|
||||
show2FAModal();
|
||||
}
|
||||
}
|
||||
|
||||
// Show 2FA modal
|
||||
function show2FAModal() {
|
||||
const modal = document.getElementById("2fa-modal");
|
||||
const setup = document.getElementById("2fa-setup");
|
||||
const verify = document.getElementById("2fa-verify");
|
||||
const loading = document.getElementById("2fa-loading");
|
||||
|
||||
modal.classList.remove("hidden");
|
||||
loading.classList.remove("hidden");
|
||||
setup.classList.add("hidden");
|
||||
verify.classList.add("hidden");
|
||||
|
||||
// Generate 2FA secret
|
||||
generate2FASecret();
|
||||
}
|
||||
|
||||
// Generate 2FA secret
|
||||
async function generate2FASecret() {
|
||||
try {
|
||||
const res = await fetch("/2fa/generate");
|
||||
if (res.ok) {
|
||||
const data = await res.json();
|
||||
|
||||
const setup = document.getElementById("2fa-setup");
|
||||
const verify = document.getElementById("2fa-verify");
|
||||
const loading = document.getElementById("2fa-loading");
|
||||
|
||||
loading.classList.add("hidden");
|
||||
setup.classList.remove("hidden");
|
||||
|
||||
// Display secret key
|
||||
document.getElementById("secret-key").textContent = data.secret;
|
||||
|
||||
// Generate QR code image
|
||||
const qrCode = document.getElementById("qr-code");
|
||||
qrCode.innerHTML = `
|
||||
<div class="bg-white p-4 rounded text-center">
|
||||
<img src="${data.qrCode}" alt="QR Code for 2FA" class="mx-auto" onerror="this.style.display='none'; this.nextElementSibling.style.display='block';" />
|
||||
<div class="text-xs text-gray-500 mt-2" style="display: none;">
|
||||
<p>QR Code failed to load. Use this URL instead:</p>
|
||||
<p class="font-mono bg-gray-100 px-2 py-1 rounded break-all">${data.qrCodeUrl}</p>
|
||||
</div>
|
||||
<p class="text-xs text-gray-500 mt-2">Scan this QR code with your authenticator app</p>
|
||||
</div>
|
||||
`;
|
||||
} else {
|
||||
throw new Error("Failed to generate 2FA secret");
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("2FA generation error:", err);
|
||||
document.getElementById("2fa-loading").innerHTML =
|
||||
'<p class="text-red-500">Error generating 2FA. Please refresh the page.</p>';
|
||||
}
|
||||
}
|
||||
|
||||
// Setup 2FA event listeners
|
||||
document.addEventListener("DOMContentLoaded", function () {
|
||||
const generateBtn = document.getElementById("generate-2fa");
|
||||
const verifyBtn = document.getElementById("verify-2fa");
|
||||
const backBtn = document.getElementById("back-to-setup");
|
||||
const tokenInput = document.getElementById("2fa-token");
|
||||
|
||||
if (generateBtn) {
|
||||
generateBtn.addEventListener("click", generate2FASecret);
|
||||
}
|
||||
|
||||
if (verifyBtn) {
|
||||
verifyBtn.addEventListener("click", verify2FA);
|
||||
}
|
||||
|
||||
if (backBtn) {
|
||||
backBtn.addEventListener("click", function () {
|
||||
document.getElementById("2fa-setup").classList.remove("hidden");
|
||||
document.getElementById("2fa-verify").classList.add("hidden");
|
||||
});
|
||||
}
|
||||
|
||||
if (tokenInput) {
|
||||
tokenInput.addEventListener("input", function () {
|
||||
if (this.value.length === 6) {
|
||||
verify2FA();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const proceedBtn = document.getElementById("proceed-to-verify");
|
||||
if (proceedBtn) {
|
||||
proceedBtn.addEventListener("click", function () {
|
||||
document.getElementById("2fa-setup").classList.add("hidden");
|
||||
document.getElementById("2fa-verify").classList.remove("hidden");
|
||||
document.getElementById("2fa-token").focus();
|
||||
});
|
||||
}
|
||||
|
||||
const downloadQrBtn = document.getElementById("download-qr");
|
||||
if (downloadQrBtn) {
|
||||
downloadQrBtn.addEventListener("click", function () {
|
||||
const qrImg = document.querySelector("#qr-code img");
|
||||
if (qrImg && qrImg.src) {
|
||||
const link = document.createElement("a");
|
||||
link.download = "2fa-qr-code.png";
|
||||
link.href = qrImg.src;
|
||||
link.click();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const copySecretBtn = document.getElementById("copy-secret");
|
||||
if (copySecretBtn) {
|
||||
copySecretBtn.addEventListener("click", function () {
|
||||
const secretKey = document.getElementById("secret-key").textContent;
|
||||
if (secretKey) {
|
||||
navigator.clipboard
|
||||
.writeText(secretKey)
|
||||
.then(() => {
|
||||
// Show temporary success message
|
||||
const originalText = copySecretBtn.textContent;
|
||||
copySecretBtn.textContent = "Copied!";
|
||||
copySecretBtn.classList.remove("bg-gray-500", "hover:bg-gray-600");
|
||||
copySecretBtn.classList.add("bg-green-500");
|
||||
setTimeout(() => {
|
||||
copySecretBtn.textContent = originalText;
|
||||
copySecretBtn.classList.remove("bg-green-500");
|
||||
copySecretBtn.classList.add("bg-gray-500", "hover:bg-gray-600");
|
||||
}, 2000);
|
||||
})
|
||||
.catch(() => {
|
||||
alert("Failed to copy to clipboard. Secret: " + secretKey);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Verify 2FA token
|
||||
async function verify2FA() {
|
||||
const token = document.getElementById("2fa-token").value.trim();
|
||||
|
||||
if (token.length !== 6) {
|
||||
alert("Please enter a 6-digit code");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const res = await fetch("/2fa/verify", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ token }),
|
||||
});
|
||||
|
||||
if (res.ok) {
|
||||
const data = await res.json();
|
||||
if (data.success) {
|
||||
twoFactorVerified = true;
|
||||
document.getElementById("2fa-modal").classList.add("hidden");
|
||||
// Enable all widgets
|
||||
enableDashboard();
|
||||
} else {
|
||||
alert("Invalid token. Please try again.");
|
||||
}
|
||||
} else {
|
||||
const errorData = await res.json();
|
||||
alert(errorData.error || "Verification failed");
|
||||
}
|
||||
} catch (err) {
|
||||
alert("Verification failed. Please try again.");
|
||||
}
|
||||
}
|
||||
|
||||
// Enable dashboard after 2FA verification
|
||||
function enableDashboard() {
|
||||
// Dashboard is already visible, just ensure all functionality is enabled
|
||||
console.log("Dashboard enabled after 2FA verification");
|
||||
}
|
||||
|
||||
// Check 2FA status on page load
|
||||
check2FAStatus();
|
||||
|
||||
// Function to update weather widget
|
||||
async function updateWeather() {
|
||||
try {
|
||||
@@ -275,6 +474,61 @@ if (exportBtn && exportBtn.textContent.trim().toLowerCase() === "export") {
|
||||
});
|
||||
}
|
||||
|
||||
// --- Import XML Widget ---
|
||||
const importXmlInput = document.getElementById("import-xml-input");
|
||||
const importXmlBtn = document.getElementById("import-xml-btn");
|
||||
const importStatus = document.getElementById("import-status");
|
||||
|
||||
if (importXmlBtn && importXmlInput && importStatus) {
|
||||
importXmlBtn.addEventListener("click", async function () {
|
||||
if (!importXmlInput.files || !importXmlInput.files[0]) {
|
||||
importStatus.textContent = "Please select an XML file";
|
||||
importStatus.className = "text-xs mt-2 text-red-500";
|
||||
return;
|
||||
}
|
||||
|
||||
const file = importXmlInput.files[0];
|
||||
|
||||
// Validate file type
|
||||
if (!file.type.includes("xml")) {
|
||||
importStatus.textContent = "Please select a valid XML file";
|
||||
importStatus.className = "text-xs mt-2 text-red-500";
|
||||
return;
|
||||
}
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append("xmlfile", file);
|
||||
|
||||
try {
|
||||
importStatus.textContent = "Importing...";
|
||||
importStatus.className = "text-xs mt-2 text-blue-500";
|
||||
|
||||
const res = await fetch("/analytic/import", {
|
||||
method: "POST",
|
||||
body: formData,
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
const errorData = await res.json();
|
||||
throw new Error(errorData.error || "Import failed");
|
||||
}
|
||||
|
||||
const data = await res.json();
|
||||
importStatus.textContent = data.message;
|
||||
importStatus.className = "text-xs mt-2 text-green-500";
|
||||
|
||||
// Clear the file input
|
||||
importXmlInput.value = "";
|
||||
|
||||
// Update the click counter to reflect new data
|
||||
setTimeout(updateClickCounter, 1000);
|
||||
} catch (err) {
|
||||
importStatus.textContent = `Error: ${err.message}`;
|
||||
importStatus.className = "text-xs mt-2 text-red-500";
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// --- Reddit News Widget ---
|
||||
async function updateRedditNews() {
|
||||
try {
|
||||
|
||||
Reference in New Issue
Block a user