mirror of
https://github.com/DeNNiiInc/UltyScan.git
synced 2026-04-17 18:26:00 +00:00
308 lines
8.5 KiB
JavaScript
308 lines
8.5 KiB
JavaScript
// UltyScan Web Interface - Frontend Logic
|
|
|
|
document.addEventListener("DOMContentLoaded", function () {
|
|
initTabs();
|
|
initModeHandler();
|
|
loadWorkspaces();
|
|
checkScanStatus();
|
|
});
|
|
|
|
// Tab Navigation
|
|
function initTabs() {
|
|
const tabs = document.querySelectorAll(".tab");
|
|
tabs.forEach((tab) => {
|
|
tab.addEventListener("click", () => {
|
|
tabs.forEach((t) => t.classList.remove("active"));
|
|
document
|
|
.querySelectorAll(".tab-content")
|
|
.forEach((c) => c.classList.remove("active"));
|
|
|
|
tab.classList.add("active");
|
|
document.getElementById(tab.dataset.tab).classList.add("active");
|
|
});
|
|
});
|
|
}
|
|
|
|
// Dynamic form fields based on mode
|
|
function initModeHandler() {
|
|
const modeSelect = document.getElementById("mode");
|
|
const portGroup = document.getElementById("port-group");
|
|
const targetFileGroup = document.getElementById("target-file-group");
|
|
const singleTargetGroup = document.getElementById("single-target-group");
|
|
|
|
if (!modeSelect) return;
|
|
|
|
modeSelect.addEventListener("change", function () {
|
|
const mode = this.value;
|
|
|
|
// Modes that require a file
|
|
const fileModes = [
|
|
"airstrike",
|
|
"nuke",
|
|
"massportscan",
|
|
"massweb",
|
|
"masswebscan",
|
|
"massvulnscan",
|
|
"flyover",
|
|
];
|
|
// Modes that require a port
|
|
const portModes = ["port", "webporthttp", "webporthttps"];
|
|
|
|
if (fileModes.includes(mode)) {
|
|
targetFileGroup.style.display = "block";
|
|
singleTargetGroup.querySelector("input").required = false;
|
|
} else {
|
|
targetFileGroup.style.display = "none";
|
|
singleTargetGroup.querySelector("input").required = true;
|
|
}
|
|
|
|
if (portModes.includes(mode)) {
|
|
portGroup.style.display = "block";
|
|
portGroup.querySelector("input").required = true;
|
|
} else {
|
|
portGroup.style.display = "none";
|
|
portGroup.querySelector("input").required = false;
|
|
}
|
|
});
|
|
}
|
|
|
|
// Submit Scan Form
|
|
async function submitScan(event) {
|
|
event.preventDefault();
|
|
|
|
const form = event.target;
|
|
const formData = new FormData(form);
|
|
const submitBtn = form.querySelector('button[type="submit"]');
|
|
const originalText = submitBtn.innerHTML;
|
|
|
|
submitBtn.innerHTML = '<span class="spinner"></span> Starting...';
|
|
submitBtn.disabled = true;
|
|
|
|
try {
|
|
const response = await fetch("execute.php", {
|
|
method: "POST",
|
|
body: formData,
|
|
});
|
|
|
|
const result = await response.json();
|
|
|
|
if (result.success) {
|
|
showNotification("Scan started successfully!", "success");
|
|
updateConsole(result.command);
|
|
startStatusPolling();
|
|
} else {
|
|
showNotification("Error: " + result.error, "error");
|
|
}
|
|
} catch (error) {
|
|
showNotification("Failed to start scan: " + error.message, "error");
|
|
} finally {
|
|
submitBtn.innerHTML = originalText;
|
|
submitBtn.disabled = false;
|
|
}
|
|
}
|
|
|
|
// Load Workspaces
|
|
async function loadWorkspaces() {
|
|
const container = document.getElementById("workspace-list");
|
|
if (!container) return;
|
|
|
|
container.innerHTML =
|
|
'<p style="color: var(--text-secondary);">Loading workspaces...</p>';
|
|
|
|
try {
|
|
const response = await fetch("workspaces.php?action=list");
|
|
const result = await response.json();
|
|
|
|
if (result.workspaces && result.workspaces.length > 0) {
|
|
container.innerHTML = result.workspaces
|
|
.map(
|
|
(ws) => `
|
|
<div class="workspace-item">
|
|
<span class="workspace-name">${escapeHtml(ws)}</span>
|
|
<div class="workspace-actions">
|
|
<button class="btn btn-secondary" onclick="viewWorkspace('${escapeHtml(
|
|
ws
|
|
)}')">View</button>
|
|
<button class="btn btn-secondary" onclick="exportWorkspace('${escapeHtml(
|
|
ws
|
|
)}')">Export</button>
|
|
<button class="btn btn-danger" onclick="deleteWorkspace('${escapeHtml(
|
|
ws
|
|
)}')">Delete</button>
|
|
</div>
|
|
</div>
|
|
`
|
|
)
|
|
.join("");
|
|
} else {
|
|
container.innerHTML =
|
|
'<p style="color: var(--text-secondary);">No workspaces found. Run a scan to create one.</p>';
|
|
}
|
|
} catch (error) {
|
|
container.innerHTML =
|
|
'<p style="color: var(--accent-danger);">Failed to load workspaces.</p>';
|
|
}
|
|
}
|
|
|
|
// Workspace Actions
|
|
async function viewWorkspace(name) {
|
|
try {
|
|
const response = await fetch(
|
|
`workspaces.php?action=view&name=${encodeURIComponent(name)}`
|
|
);
|
|
const result = await response.json();
|
|
|
|
if (result.reportPath) {
|
|
window.open(result.reportPath, "_blank");
|
|
} else {
|
|
showNotification("No report found for this workspace.", "warning");
|
|
}
|
|
} catch (error) {
|
|
showNotification("Failed to open workspace.", "error");
|
|
}
|
|
}
|
|
|
|
async function exportWorkspace(name) {
|
|
showNotification("Exporting workspace: " + name, "info");
|
|
try {
|
|
const response = await fetch("workspaces.php", {
|
|
method: "POST",
|
|
headers: { "Content-Type": "application/json" },
|
|
body: JSON.stringify({ action: "export", name: name }),
|
|
});
|
|
const result = await response.json();
|
|
|
|
if (result.success) {
|
|
showNotification("Workspace exported: " + result.path, "success");
|
|
} else {
|
|
showNotification("Export failed: " + result.error, "error");
|
|
}
|
|
} catch (error) {
|
|
showNotification("Export failed.", "error");
|
|
}
|
|
}
|
|
|
|
async function deleteWorkspace(name) {
|
|
if (!confirm(`Are you sure you want to delete workspace "${name}"?`)) return;
|
|
|
|
try {
|
|
const response = await fetch("workspaces.php", {
|
|
method: "POST",
|
|
headers: { "Content-Type": "application/json" },
|
|
body: JSON.stringify({ action: "delete", name: name }),
|
|
});
|
|
const result = await response.json();
|
|
|
|
if (result.success) {
|
|
showNotification("Workspace deleted.", "success");
|
|
loadWorkspaces();
|
|
} else {
|
|
showNotification("Delete failed: " + result.error, "error");
|
|
}
|
|
} catch (error) {
|
|
showNotification("Delete failed.", "error");
|
|
}
|
|
}
|
|
|
|
// Check Scan Status
|
|
let statusInterval = null;
|
|
|
|
async function checkScanStatus() {
|
|
const statusBadge = document.getElementById("scan-status");
|
|
if (!statusBadge) return;
|
|
|
|
try {
|
|
const response = await fetch("status.php");
|
|
const result = await response.json();
|
|
|
|
if (result.running) {
|
|
statusBadge.className = "status-badge status-running";
|
|
statusBadge.innerHTML = '<span class="spinner"></span> Scan Running';
|
|
startStatusPolling();
|
|
} else {
|
|
statusBadge.className = "status-badge status-idle";
|
|
statusBadge.textContent = "Idle";
|
|
stopStatusPolling();
|
|
}
|
|
} catch (error) {
|
|
console.error("Status check failed:", error);
|
|
}
|
|
}
|
|
|
|
function startStatusPolling() {
|
|
if (statusInterval) return;
|
|
statusInterval = setInterval(checkScanStatus, 5000);
|
|
}
|
|
|
|
function stopStatusPolling() {
|
|
if (statusInterval) {
|
|
clearInterval(statusInterval);
|
|
statusInterval = null;
|
|
}
|
|
}
|
|
|
|
// Console Output
|
|
function updateConsole(text) {
|
|
const console = document.getElementById("console-output");
|
|
if (console) {
|
|
console.textContent += "\n$ " + text;
|
|
console.scrollTop = console.scrollHeight;
|
|
}
|
|
}
|
|
|
|
// Notifications
|
|
function showNotification(message, type = "info") {
|
|
// Create notification element
|
|
const notification = document.createElement("div");
|
|
notification.style.cssText = `
|
|
position: fixed;
|
|
top: 20px;
|
|
right: 20px;
|
|
padding: 1rem 1.5rem;
|
|
border-radius: 8px;
|
|
color: white;
|
|
font-weight: 500;
|
|
z-index: 9999;
|
|
animation: slideIn 0.3s ease;
|
|
max-width: 400px;
|
|
`;
|
|
|
|
const colors = {
|
|
success: "var(--accent-success)",
|
|
error: "var(--accent-danger)",
|
|
warning: "var(--accent-warning)",
|
|
info: "var(--accent-primary)",
|
|
};
|
|
|
|
notification.style.background = colors[type] || colors.info;
|
|
notification.textContent = message;
|
|
|
|
document.body.appendChild(notification);
|
|
|
|
setTimeout(() => {
|
|
notification.style.animation = "slideOut 0.3s ease forwards";
|
|
setTimeout(() => notification.remove(), 300);
|
|
}, 4000);
|
|
}
|
|
|
|
// Utility
|
|
function escapeHtml(text) {
|
|
const div = document.createElement("div");
|
|
div.textContent = text;
|
|
return div.innerHTML;
|
|
}
|
|
|
|
// Add animation keyframes
|
|
const style = document.createElement("style");
|
|
style.textContent = `
|
|
@keyframes slideIn {
|
|
from { transform: translateX(100%); opacity: 0; }
|
|
to { transform: translateX(0); opacity: 1; }
|
|
}
|
|
@keyframes slideOut {
|
|
from { transform: translateX(0); opacity: 1; }
|
|
to { transform: translateX(100%); opacity: 0; }
|
|
}
|
|
`;
|
|
document.head.appendChild(style);
|