Files
MythEclipse c6fd9ec268 feat: challenge registry-driven platform + XVI/XVII imports + admin toggle + domain rename
- Rename repo/domain: attack-defense-platform / attackdefense.imrnes.team (all refs replaced)
- challenge_registry.json: single source of truth (28 challs across gemastik18/xvi/xvii)
- teams.py: registry-driven CHALLENGES, set_challenge_enabled, sync_challenge_runtime
  (apply enable/disable to live teams: build/up or stop/remove + receiver restart)
- compose_gen.py: render per-team compose from canonical per-challenge templates
  (image reuse, per-team ports 30xxx, flag mounts, passwords)
- gen_canonical_composes.py: canonical docker-compose.yml for all services
- import_new_challenges.py: import XVI/XVII services + EOL base image fixes
  (debian:buster→bookworm, node:14→20, python:3.7-slim→3.11)
- receiver: xvi package (10 checkers) + xvii package (12 generic checkers),
  Challenge base reads PASSWORD_<team_port> from env; gen_receiver_main.py
  generates per-team main.py from registry
- main.py: /api/challenges returns full registry; PATCH /api/challenges/<name>
  toggles enabled + applies to live teams
- index.html: 🏗️ Challenge Manager tab (toggle per challenge, grouped by set)
- SLA bonus now dynamic (all enabled challenges, not hardcoded 6)
2026-09-25 14:04:33 +08:00

90 lines
3.0 KiB
JavaScript

document.addEventListener("DOMContentLoaded", () => {
const startScanButton = document.getElementById("startScan");
const testConnectionButton = document.getElementById("testConnection");
const statusAndResultsDiv = document.getElementById("statusAndResults");
const urlInput = document.getElementById("target");
if (!crypto.randomUUID) {
crypto.randomUUID = function () {
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
var r = (crypto.getRandomValues(new Uint8Array(1))[0] % 16) | 0,
v = c === 'x' ? r : (r & 0x3) | 0x8;
return v.toString(16);
});
};
}
const uuid = crypto.randomUUID();
const socket = new WebSocket(`ws://${window.location.host}/api/public/ws/?client_id=${uuid}`);
function addStatusItem(message, className = '') {
const item = document.createElement('div');
item.textContent = message;
item.className = `status-item ${className}`;
statusAndResultsDiv.appendChild(item);
statusAndResultsDiv.scrollTop = statusAndResultsDiv.scrollHeight;
}
socket.addEventListener("message", (event) => {
const data = JSON.parse(event.data);
addStatusItem(data.data);
});
startScanButton.addEventListener("click", () => {
statusAndResultsDiv.innerHTML = '';
const target = urlInput.value;
if (!target) return;
startScanButton.disabled = true;
fetch("/api/public/startscan", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({ target: target, client_id: uuid })
})
.then(response => response.json())
.then(data => {
if (data.error) {
addStatusItem(data.error, 'error');
} else {
addStatusItem('Scan initiated. Monitoring progress...', 'success');
}
})
.catch(error => {
addStatusItem(`Error: ${error.message}`, 'error');
})
.finally(() => {
urlInput.value = '';
});
});
testConnectionButton.addEventListener("click", () => {
statusAndResultsDiv.innerHTML = '';
const url = urlInput.value;
if (!url) return;
testConnectionButton.disabled = true;
fetch("/api/public/test", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({ url: url })
})
.then(response => {
if (!response.ok && response.status != 301) {
addStatusItem(`Target unreachable. Status Code: ${response.status}`, 'error');
} else {
addStatusItem(`Target accessible. Status Code: ${response.status}`, 'success');
startScanButton.disabled = false;
}
})
.catch(error => {
addStatusItem(`Error: ${error.message}`, 'error');
})
.finally(() => {
testConnectionButton.disabled = false;
});
});
});