feat: target matrix + reset scores/environment buttons
- /api/targets (admin): matrix of all teams' domain:port targets - /api/reset/scores: wipe leaderboard (admin, confirm dialog) - /api/reset/environment: stop all teams, remove containers+receivers+ team dirs+systemd units, wipe scores, drop domains (admin, confirm) - portal targets now only domain+port (no ssh/labels) - UI: tab Target Matrix, header buttons Reset Skor / Reset Environment with confirm() alerts 'apakah anda yakin ingin mereset...'
This commit is contained in:
@@ -115,6 +115,8 @@
|
||||
<div class="actions">
|
||||
<span class="pill" id="clock">--:--:--</span>
|
||||
<button onclick="refresh()">🔄 Refresh</button>
|
||||
<button class="danger" onclick="resetScores()" style="background:#dc2626;border:none;border-radius:8px;padding:8px 14px;color:#fff;font-weight:600;cursor:pointer">🧹 Reset Skor</button>
|
||||
<button class="danger" onclick="resetEnv()" style="background:#7f1d1d;border:none;border-radius:8px;padding:8px 14px;color:#fff;font-weight:600;cursor:pointer">💥 Reset Environment</button>
|
||||
<button onclick="logout()">Keluar</button>
|
||||
</div>
|
||||
</header>
|
||||
@@ -125,6 +127,7 @@
|
||||
<button class="tab" data-view="teams" onclick="showView('teams'); loadTeams()">👥 Teams</button>
|
||||
<button class="tab" data-view="logs" onclick="showView('logs'); loadLogs()">📜 Logs</button>
|
||||
<button class="tab" data-view="creds" onclick="showView('creds'); loadCreds()">🔑 Creds</button>
|
||||
<button class="tab" data-view="targets" onclick="showView('targets'); loadTargetMatrix()">🎯 Target Matrix</button>
|
||||
</div>
|
||||
|
||||
<!-- Challenges view -->
|
||||
@@ -190,6 +193,17 @@
|
||||
<div class="grid" id="credsGrid"></div>
|
||||
</div>
|
||||
|
||||
<!-- Target Matrix view -->
|
||||
<div class="view" id="view-targets">
|
||||
<div class="card">
|
||||
<div style="display:flex;align-items:center;gap:10px;flex-wrap:wrap">
|
||||
<b style="color:#5ad1ff">🎯 Target Matrix</b>
|
||||
<span style="font-size:11px;color:#718096">Domain:port semua service semua tim — buat dibagikan ke peserta sebagai daftar target</span>
|
||||
</div>
|
||||
<div id="targetMatrix" style="margin-top:14px;font-family:ui-monospace,monospace;font-size:12px;line-height:1.9;color:#dbe6f4;background:#0a101f;border:1px solid #1e3a5f;border-radius:10px;padding:14px;overflow-x:auto">Memuat…</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="footer-note">Receiver: https://gemastik.imrnes.team · Panel: https://panel.gemastik.imrnes.team · Auto-refresh tiap 15 detik</div>
|
||||
|
||||
<!-- modal flag -->
|
||||
@@ -519,6 +533,28 @@ async function stopAllTeams() {
|
||||
finally { hideLoading(); }
|
||||
}
|
||||
|
||||
async function resetScores() {
|
||||
if (!confirm('⚠️ Apakah anda yakin ingin mereset skor?\n\nSemua poin & solve di leaderboard akan DIHAPUS.\nTindakan ini tidak bisa dibatalkan.')) return;
|
||||
showLoading('Menghapus semua skor…');
|
||||
try {
|
||||
const d = await api('/api/reset/scores', {method:'POST', headers:{'Content-Type':'application/json'}, body: '{}'});
|
||||
toast('Skor direset: semua solve dihapus', false);
|
||||
} catch (e) { toast(e.message, true); }
|
||||
finally { hideLoading(); }
|
||||
}
|
||||
|
||||
async function resetEnv() {
|
||||
if (!confirm('⚠️ Apakah anda yakin ingin mereset environment?\n\nSEMUA team akan DISTOP:\n• Container challenge dihapus\n• Receiver team dihapus\n• Folder team dihapus (perlu create ulang)\n• Skor leaderboard direset\n\nTindakan ini TIDAK BISA dibatalkan.')) return;
|
||||
showLoading('Reset environment…\nMenghentikan semua team + menghapus container/receiver (beberapa menit)');
|
||||
try {
|
||||
const d = await api('/api/reset/environment', {method:'POST', headers:{'Content-Type':'application/json'}, body: '{}'});
|
||||
const n = (d.stopped || []).length;
|
||||
toast(`Environment direset: ${n} team dihentikan & dihapus`, false);
|
||||
setTimeout(() => location.reload(), 1200);
|
||||
} catch (e) { toast(e.message, true); }
|
||||
finally { hideLoading(); }
|
||||
}
|
||||
|
||||
async function randomizeTeam(idx) {
|
||||
if (!confirm(`Randomize SEMUA flag untuk Team ${idx}? Container akan di-recreate.`)) return;
|
||||
try {
|
||||
@@ -619,6 +655,27 @@ async function loadCreds() {
|
||||
} catch (e) { toast(e.message, true); }
|
||||
}
|
||||
|
||||
// ---------- Target Matrix ----------
|
||||
async function loadTargetMatrix() {
|
||||
const box = document.getElementById('targetMatrix');
|
||||
try {
|
||||
const d = await api('/api/targets');
|
||||
const targets = d.targets || [];
|
||||
if (!targets.length) { box.textContent = 'Belum ada team.'; return; }
|
||||
// group by team
|
||||
const rows = [];
|
||||
let curTeam = null;
|
||||
for (const t of targets) {
|
||||
if (t.team !== curTeam) {
|
||||
rows.push(`\n[${esc(t.team_label || ('Team ' + t.team))}]`);
|
||||
curTeam = t.team;
|
||||
}
|
||||
rows.push(` ${esc(t.domain)}:${t.port} (${esc(t.challenge)})`);
|
||||
}
|
||||
box.textContent = rows.join('\n');
|
||||
} catch (e) { box.textContent = 'Gagal: ' + e.message; }
|
||||
}
|
||||
|
||||
// ---------- misc ----------
|
||||
function closeModal(id) { document.getElementById(id).classList.remove('open'); }
|
||||
async function logout() {
|
||||
|
||||
@@ -264,10 +264,7 @@ async function loadTargets() {
|
||||
if (!targets.length) { box.textContent = 'Belum ada tim musuh.'; return; }
|
||||
let html = '=== TARGET MUSUH ===\n\n';
|
||||
for (const t of targets) {
|
||||
html += `[${esc(t.team)}] — ${esc(t.challenge)}\n`;
|
||||
if (t.domain) html += ` domain : https://${esc(t.domain)}\n`;
|
||||
html += ` service: ${esc(window.location.hostname)}:${t.port}\n`;
|
||||
html += ` ssh : ${esc(window.location.hostname)}:${t.ssh}\n\n`;
|
||||
html += `${esc(t.domain)}:${t.port}\n`;
|
||||
}
|
||||
box.textContent = html;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user