prob fix 2.0
This commit is contained in:
parent
ca61740f07
commit
bada4b312a
@ -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 · 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,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"');}
|
||||
|
||||
/* ── 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>
|
||||
|
||||
13
server.js
13
server.js
@ -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 ──────────────────────────────────────
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user