- 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)
90 lines
3.0 KiB
JavaScript
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;
|
|
});
|
|
});
|
|
}); |