prob fix 2.0

This commit is contained in:
Jeremy Kirsch 2026-05-18 15:26:24 +02:00
parent ca61740f07
commit bada4b312a
2 changed files with 90 additions and 30 deletions

View File

@ -294,13 +294,13 @@ body::before{content:'';position:fixed;inset:0;background-image:radial-gradient(
<img class="auth-avatar" id="auth-av" src="" alt="">
<span class="auth-name" id="auth-nm"></span>
<span class="auth-role" id="auth-rl"></span>
<button class="btn-logout" onclick="doLogout()">Abmelden</button>
<button class="btn-logout">Abmelden</button>
</div>
<span id="guest-badge" style="display:none;font-family:'Share Tech Mono',monospace;font-size:9px;letter-spacing:2px;color:var(--muted);">GAST</span>
<div class="mode-pills">
<button class="mode-pill active" onclick="setMode('viewer')">👁 Viewer</button>
<button class="mode-pill up" id="pill-user" onclick="setMode('user')" style="display:none;">✦ Community<span class="pill-badge" id="badge-user"></span></button>
<button class="mode-pill ap" id="pill-admin" onclick="setMode('admin')" style="display:none;">⚙ Admin<span class="pill-badge" id="badge-admin"></span></button>
<button class="mode-pill active" id="pill-viewer">👁 Viewer</button>
<button class="mode-pill up" id="pill-user" style="display:none;">✦ Community<span class="pill-badge" id="badge-user"></span></button>
<button class="mode-pill ap" id="pill-admin" style="display:none;">⚙ Admin<span class="pill-badge" id="badge-admin"></span></button>
</div>
</div>
</div>
@ -309,9 +309,9 @@ body::before{content:'';position:fixed;inset:0;background-image:radial-gradient(
<!-- VIEWER -->
<div id="mode-viewer">
<nav class="top-nav" id="vnav">
<button class="nav-btn active" onclick="switchTab('cards')">Ghost Cards</button>
<button class="nav-btn" onclick="switchTab('cheat')">Cheatsheet</button>
<button class="nav-btn" onclick="switchTab('guide')">Hunt Guide</button>
<button class="nav-btn active">Ghost Cards</button>
<button class="nav-btn">Cheatsheet</button>
<button class="nav-btn">Hunt Guide</button>
</nav>
<div class="panel active" id="vtab-cards">
<div class="filter-row" id="ev-filter"><div class="filter-label-sm">Evidence Filter — einmal: gefunden &nbsp;·&nbsp; zweimal: ausgeschlossen</div></div>
@ -354,8 +354,8 @@ body::before{content:'';position:fixed;inset:0;background-image:radial-gradient(
<div class="admin-header">
<div><div class="admin-title">⚙ Admin Dashboard</div><div class="admin-sub">Ghost Database Management · Nur für Whitelist-Admins</div></div>
<div style="display:flex;gap:8px;flex-wrap:wrap;">
<button class="btn-accent" onclick="openGhostForm(null)">+ New Ghost</button>
<button class="btn-sm" onclick="exportGhosts()">↓ Export</button>
<button class="btn-accent" id="btn-new-ghost">+ New Ghost</button>
<button class="btn-sm" id="btn-export">↓ Export</button>
</div>
</div>
<div class="stats-row" id="admin-stats"></div>
@ -365,7 +365,7 @@ body::before{content:'';position:fixed;inset:0;background-image:radial-gradient(
<div style="display:flex;gap:8px;flex-wrap:wrap;align-items:center;">
<input type="text" class="form-input" id="wl-id" placeholder="Discord ID (18-stellig)" style="width:210px;">
<input type="text" class="form-input" id="wl-name" placeholder="Username (optional)" style="width:160px;">
<button class="btn-success" onclick="addAdmin()">+ Hinzufügen</button>
<button class="btn-success" id="btn-add-admin">+ Hinzufügen</button>
</div>
<div class="form-hint" style="margin-top:8px;">Discord → Einstellungen → Erweitert → Entwicklermodus → Rechtsklick auf Nutzer → ID kopieren</div>
</div>
@ -398,7 +398,7 @@ body::before{content:'';position:fixed;inset:0;background-image:radial-gradient(
<span class="form-hint">Je detaillierter, desto besser für den Admin!</span>
</div>
</div>
<div class="form-footer"><button class="btn-blue" onclick="submitTip()">Einreichen →</button></div>
<div class="form-footer"><button class="btn-blue" id="btn-submit-tip">Einreichen →</button></div>
</div>
</div>
<div class="divider"><div class="divider-line"></div><div class="divider-text">Deine Einreichungen</div><div class="divider-line"></div></div>
@ -407,16 +407,16 @@ body::before{content:'';position:fixed;inset:0;background-image:radial-gradient(
</div>
<!-- Ghost View Modal -->
<div class="modal-bg" id="ghost-modal" onclick="if(event.target===this)closeModal('ghost-modal')">
<div class="modal-box"><button class="modal-close" onclick="closeModal('ghost-modal')"></button>
<div class="modal-bg" id="ghost-modal">
<div class="modal-box"><button class="modal-close" id="close-ghost-modal"></button>
<div class="modal-hd"><div class="modal-title" id="gm-title"></div><div class="modal-sub" id="gm-sub"></div></div>
<div class="modal-body" id="gm-body"></div>
</div>
</div>
<!-- Ghost Editor Modal -->
<div class="modal-bg" id="edit-modal" onclick="if(event.target===this)closeModal('edit-modal')">
<div class="modal-box wide"><button class="modal-close" onclick="closeModal('edit-modal')"></button>
<div class="modal-bg" id="edit-modal">
<div class="modal-box wide"><button class="modal-close" id="close-edit-modal"></button>
<div class="modal-hd"><div class="modal-title" id="em-title">Geist bearbeiten</div><div class="modal-sub" id="em-sub">Admin · Ghost Editor</div></div>
<div class="modal-body">
<div class="form-grid">
@ -436,11 +436,11 @@ body::before{content:'';position:fixed;inset:0;background-image:radial-gradient(
<div class="form-group">
<label class="form-label">Identification Tells</label>
<div class="tells-editor" id="ef-tells"></div>
<button class="add-tell-btn" onclick="addTellRow()">+ Tell hinzufügen</button>
<button class="add-tell-btn" id="btn-add-tell">+ Tell hinzufügen</button>
</div>
<div class="form-footer">
<button class="btn-sm" onclick="closeModal('edit-modal')">Abbrechen</button>
<button class="btn-accent" onclick="saveGhost()">Speichern</button>
<button class="btn-sm" id="btn-cancel-edit">Abbrechen</button>
<button class="btn-accent" id="btn-save-ghost">Speichern</button>
</div>
</div>
</div>
@ -451,7 +451,7 @@ body::before{content:'';position:fixed;inset:0;background-image:radial-gradient(
<div class="confirm-box">
<div class="confirm-title" id="confirm-title">Sicher?</div>
<div class="confirm-text" id="confirm-text"></div>
<div class="confirm-btns"><button class="btn-sm" onclick="closeConfirm()">Abbrechen</button><button class="btn-danger" id="confirm-ok">Bestätigen</button></div>
<div class="confirm-btns"><button class="btn-sm">Abbrechen</button><button class="btn-danger" id="confirm-ok">Bestätigen</button></div>
</div>
</div>
<div class="toast-wrap" id="toast-wrap"></div>
@ -846,7 +846,74 @@ function fmtTime(ts){if(!ts)return'—';const d=new Date(ts);return d.toLocaleDa
function escH(s){return String(s||'').replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/"/g,'&quot;');}
/* ── BOOT ── */
init();
document.addEventListener('DOMContentLoaded', () => {
// ── Mode pills ──
document.getElementById('pill-viewer').addEventListener('click', () => setMode('viewer'));
const pu = document.getElementById('pill-user');
const pa = document.getElementById('pill-admin');
if (pu) pu.addEventListener('click', () => setMode('user'));
if (pa) pa.addEventListener('click', () => setMode('admin'));
// ── Viewer tabs ──
document.querySelectorAll('#vnav .nav-btn').forEach((btn, i) => {
const tabs = ['cards', 'cheat', 'guide'];
btn.addEventListener('click', () => switchTab(tabs[i]));
});
// ── Auth ──
const logoutBtn = document.querySelector('.btn-logout');
if (logoutBtn) logoutBtn.addEventListener('click', doLogout);
// ── Admin actions ──
const newGhost = document.getElementById('btn-new-ghost');
const exportBtn = document.getElementById('btn-export');
const addAdminBtn = document.getElementById('btn-add-admin');
if (newGhost) newGhost.addEventListener('click', () => openGhostForm(null));
if (exportBtn) exportBtn.addEventListener('click', exportGhosts);
if (addAdminBtn) addAdminBtn.addEventListener('click', addAdmin);
// ── Community ──
const submitBtn = document.getElementById('btn-submit-tip');
if (submitBtn) submitBtn.addEventListener('click', submitTip);
// ── Ghost view modal ──
document.getElementById('ghost-modal').addEventListener('click', (e) => {
if (e.target === document.getElementById('ghost-modal')) closeModal('ghost-modal');
});
document.getElementById('close-ghost-modal').addEventListener('click', () => closeModal('ghost-modal'));
// ── Edit modal ──
document.getElementById('edit-modal').addEventListener('click', (e) => {
if (e.target === document.getElementById('edit-modal')) closeModal('edit-modal');
});
document.getElementById('close-edit-modal').addEventListener('click', () => closeModal('edit-modal'));
document.getElementById('btn-cancel-edit').addEventListener('click', () => closeModal('edit-modal'));
document.getElementById('btn-add-tell').addEventListener('click', addTellRow);
document.getElementById('btn-save-ghost').addEventListener('click', saveGhost);
// ── Confirm dialog ──
document.querySelector('.confirm-bg .btn-sm').addEventListener('click', closeConfirm);
document.getElementById('confirm-ok').addEventListener('click', () => {
if (confirmCb) confirmCb();
closeConfirm();
});
// ── Keyboard shortcuts ──
document.addEventListener('keydown', e => {
if (e.key === 'Escape') {
closeModal('ghost-modal');
closeModal('edit-modal');
closeConfirm();
}
});
// ── Guest button ──
const guestBtn = document.getElementById('guest-btn');
if (guestBtn) guestBtn.addEventListener('click', guestMode);
init();
});
</script>
</body>
</html>

View File

@ -16,17 +16,10 @@ const PORT = process.env.PORT || 3000;
app.set('trust proxy', 1);
// ── Security headers ──────────────────────────────────
// CSP deaktiviert — inline scripts und onclick werden sonst vom Browser geblockt
// Sicher weil self-hosted und kein user-generated HTML gerendert wird
app.use(helmet({
contentSecurityPolicy: {
directives: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'", "'unsafe-inline'", "fonts.googleapis.com"],
styleSrc: ["'self'", "'unsafe-inline'", "fonts.googleapis.com", "fonts.gstatic.com"],
fontSrc: ["'self'", "fonts.gstatic.com", "fonts.googleapis.com"],
imgSrc: ["'self'", "data:", "cdn.discordapp.com"],
connectSrc: ["'self'", "discord.com"],
},
},
contentSecurityPolicy: false,
}));
// ── Body parsers ──────────────────────────────────────