Files
websitedev/scripts/cms-pages.js
T
Do Siki 3a5a09c361
CI Pipeline with Test Management / 🧪 Run Tests & Generate Reports (push) Waiting to run
CI Pipeline with Test Management / 🐳 Docker Integration Tests (push) Blocked by required conditions
CI Pipeline with Test Management / 🏗️ Build Docker Image (push) Blocked by required conditions
CI Pipeline with Test Management / 📊 Generate Test Summary (push) Blocked by required conditions
Test Reporting & Gherkin Analysis / 🧪 Run Tests & Generate Reports (push) Waiting to run
Test Reporting & Gherkin Analysis / 📊 Analyze Test Coverage (push) Blocked by required conditions
Test Reporting & Gherkin Analysis / 🔄 Sync with Linear (push) Blocked by required conditions
Test Reporting & Gherkin Analysis / ⚡ Performance Monitoring (push) Blocked by required conditions
feat: expose deploy version (git SHA) on CMS and health endpoint
'Is the fix live?' becomes a single check instead of an SSH session:

- CMS: git short SHA read at startup, shown in the bottom bar (v<sha>),
  served by the public GET /version endpoint, recorded in a startup audit
  entry
- Website: deploy.sh exports DEPLOY_VERSION (git SHA), Dockerfile bakes it
  via build ARG into the runtime env, /api/health reports it as
  deployVersion, smoke test asserts a non-'unversioned' stamp

Closes MITHOME-63
2026-08-18 21:19:43 +02:00

241 lines
14 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Page templates for the Content Editor. Kept separate so content-editor.js
// stays focused on routing/handling and below the file-size limits.
const isStaging = () => process.env.CMS_DEPLOY_ENV === 'staging';
// FILE_LABELS is injected to avoid a circular dependency with the main file.
const HTML = (activeFile, jsonData, message, csrfToken, fileLabels, clientJs, contentHash, deployVersion) => `<!DOCTYPE html>
<html lang="hu">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>${isStaging() ? 'STAGING — ' : ''}mozdIT Content Editor</title>
<style>
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; background: #0f1117; color: #e2e8f0; min-height: 100vh; }
.environment-banner { background: #f59e0b; color: #111827; padding: 9px 32px; text-align: center; font-size: 13px; font-weight: 800; letter-spacing: .08em; text-transform: uppercase; }
header { background: linear-gradient(135deg,#1a1f2e,#252d40); border-bottom: 1px solid #2d3748; padding: 14px 32px; display: flex; align-items: center; gap: 12px; }
header h1 { font-size: 17px; font-weight: 700; background: linear-gradient(135deg,#60a5fa,#a78bfa); -webkit-background-clip: text; -webkit-text-fill-color: transparent; }
header span { color: #64748b; font-size: 13px; }
.tabs { display: flex; gap: 2px; padding: 14px 32px 0; border-bottom: 1px solid #2d3748; background: #13192a; }
.tab { text-decoration: none; color: #94a3b8; padding: 9px 16px; border-radius: 8px 8px 0 0; font-size: 13px; font-weight: 500; transition: all .2s; border: 1px solid transparent; border-bottom: none; margin-bottom: -1px; }
.tab:hover { color: #e2e8f0; background: #1e2535; }
.tab.active { color: #60a5fa; background: #0f1117; border-color: #2d3748; }
.page { max-width: 860px; margin: 28px auto 120px; padding: 0 24px; }
.hint { color: #475569; font-size: 12px; margin-bottom: 20px; }
/* Primitive field */
.field { background: #1a2035; border: 1px solid #2d3748; border-radius: 10px; padding: 14px 16px; transition: border-color .2s; margin-bottom: 10px; }
.field:focus-within { border-color: #60a5fa; }
.field label { display: block; font-size: 11px; font-weight: 600; color: #60a5fa; text-transform: uppercase; letter-spacing:.05em; margin-bottom: 7px; font-family: monospace; }
.field input, .field textarea { width: 100%; background: transparent; border: none; outline: none; color: #e2e8f0; font-size: 14px; line-height: 1.6; resize: vertical; font-family: inherit; }
.field textarea { min-height: 52px; }
/* Array section */
.array-section { margin-bottom: 20px; }
.array-label { font-size: 12px; font-weight: 700; color: #a78bfa; text-transform: uppercase; letter-spacing:.06em; font-family: monospace; margin-bottom: 10px; display: flex; align-items: center; gap: 8px; }
.array-label::after { content:''; flex: 1; height: 1px; background: #2d3748; }
.array-items { display: flex; flex-direction: column; gap: 8px; }
/* Simple string array item */
.str-item { display: flex; gap: 8px; align-items: flex-start; }
.str-item textarea { flex: 1; background: #1a2035; border: 1px solid #2d3748; border-radius: 8px; padding: 10px 12px; color: #e2e8f0; font-size: 14px; font-family: inherit; outline: none; resize: vertical; min-height: 44px; transition: border-color .2s; }
.str-item textarea:focus { border-color: #60a5fa; }
/* Object array item (card) */
.obj-card { background: #1a2035; border: 1px solid #2d3748; border-radius: 10px; padding: 14px; position: relative; }
.obj-card .card-header { font-size: 11px; color: #64748b; font-family: monospace; margin-bottom: 10px; }
.obj-card .inner-field { margin-bottom: 8px; }
.obj-card .inner-field:last-child { margin-bottom: 0; }
.obj-card .inner-label { font-size: 10px; font-weight: 600; color: #94a3b8; text-transform: uppercase; letter-spacing:.05em; font-family: monospace; margin-bottom: 4px; }
.obj-card input, .obj-card textarea { width: 100%; background: #0f1420; border: 1px solid #2d3748; border-radius: 6px; padding: 8px 10px; color: #e2e8f0; font-size: 13px; font-family: inherit; outline: none; resize: vertical; transition: border-color .2s; }
.obj-card input:focus, .obj-card textarea:focus { border-color: #60a5fa; }
/* Buttons */
.btn-del { background: transparent; border: 1px solid #3f1c1c; color: #f87171; border-radius: 7px; padding: 6px 10px; cursor: pointer; font-size: 13px; transition: all .2s; white-space: nowrap; flex-shrink: 0; }
.btn-del:hover { background: #3f1c1c; }
.btn-del-card { position: absolute; top: 10px; right: 10px; background: transparent; border: 1px solid #3f1c1c; color: #f87171; border-radius: 6px; padding: 4px 8px; cursor: pointer; font-size: 12px; transition: all .2s; }
.btn-del-card:hover { background: #3f1c1c; }
.btn-add { background: transparent; border: 1px dashed #334155; color: #64748b; border-radius: 8px; padding: 9px 16px; cursor: pointer; font-size: 13px; width: 100%; text-align: center; transition: all .2s; margin-top: 6px; }
.btn-add:hover { border-color: #a78bfa; color: #a78bfa; background: #1a1535; }
/* Bottom bar */
.bottom-bar { position: fixed; bottom: 0; left: 0; right: 0; background: #0f1117; border-top: 1px solid #2d3748; padding: 14px 32px; display: flex; gap: 14px; align-items: center; z-index: 50; }
.btn-logout { margin-left: auto; background: #1f2937; color: #e2e8f0; border: 1px solid #374151; border-radius: 8px; padding: 9px 16px; font-size: 14px; cursor: pointer; }
.btn-logout:hover { background: #374151; }
.version-tag { color: #475569; font-size: 12px; font-family: monospace; }
.btn-save { background: linear-gradient(135deg,#3b82f6,#6366f1); color: #fff; border: none; padding: 11px 26px; border-radius: 8px; font-size: 14px; font-weight: 600; cursor: pointer; transition: opacity .2s, transform .1s; }
.btn-save:hover { opacity: .9; transform: translateY(-1px); }
.btn-save:active { transform: translateY(0); }
.btn-publish { background: linear-gradient(135deg,#10b981,#059669); color: #fff; border: none; padding: 11px 26px; border-radius: 8px; font-size: 14px; font-weight: 600; cursor: pointer; transition: opacity .2s, transform .1s; }
.btn-publish:hover { opacity: .9; transform: translateY(-1px); }
.btn-publish:active { transform: translateY(0); }
.preview-link { color: #64748b; font-size: 13px; text-decoration: none; margin-left: auto; }
.preview-link:hover { color: #94a3b8; }
.save-status { font-size: 13px; font-weight: 500; display: none; margin-left: 8px; }
/* Toast */
.toast { position: fixed; top: 20px; right: 20px; padding: 13px 18px; border-radius: 9px; font-size: 14px; font-weight: 500; z-index: 200; animation: slideIn .3s ease; }
.toast.ok { background: #064e3b; border: 1px solid #10b981; color: #6ee7b7; }
.toast.err { background: #450a0a; border: 1px solid #ef4444; color: #fca5a5; }
@keyframes slideIn { from { opacity:0; transform: translateX(20px); } to { opacity:1; transform: translateX(0); } }
</style>
</head>
<body>
${isStaging() ? '<div class="environment-banner">⚠ STAGING / TESZTKÖRNYEZET — itt végzett publikálás csak a staging oldalt frissíti</div>' : ''}
${message ? `<div class="toast ${message.type === 'ok' ? 'ok' : 'err'}">${message.text}</div>` : ''}
<header>
<h1>mozdIT Content Editor</h1>
<span>— JSON fájlok szerkesztése vizuálisan</span>
</header>
<nav class="tabs">
${Object.entries(fileLabels).map(([k, l]) =>
`<a href="/?file=${k}" class="tab ${activeFile === k ? 'active' : ''}">${l}</a>`
).join('')}
</nav>
<div class="page">
<p class="hint">📝 Szerkeszd a mezőket. Tömbökből elemet törölhetsz (❌) vagy hozzáadhatsz (➕). Mentés gomb menti a fájlt.</p>
<div id="editor"></div>
</div>
<div class="bottom-bar">
<button class="btn-save" onclick="save()">💾 Mentés</button>
<button class="btn-publish" onclick="publish()" id="publishBtn">🚀 Publikálás & ${isStaging() ? 'Staging deploy' : 'Élesítés'}</button>
<span class="save-status" id="saveStatus"></span>
<a href="${isStaging() ? 'https://stage.mozdit.hu' : 'http://localhost:3000'}" target="_blank" class="preview-link">🔗 Előnézet →</a>
<a href="/guide" target="_blank" class="preview-link">❓ Súgó</a>
<span class="version-tag" title="Futó kód verziója (git SHA)">v${deployVersion}</span>
<button class="btn-logout" onclick="logout()">🚪 Kilépés</button>
</div>
<script id="page-data" type="application/json">${jsonData.replace(/<\//g, '<\\/')}</script>
<script>
const DATA = JSON.parse(document.getElementById('page-data').textContent);
const FILE = "${activeFile}";
const CSRF_TOKEN = "${csrfToken}";
const CONTENT_HASH = "${contentHash}";
${clientJs}
</script>
</script>
</body>
</html>`;
// User guide page — renders docs/felhasznaloi-utmutato.md with the shared dark theme.
const GUIDE_PAGE = (contentHtml) => `<!DOCTYPE html>
<html lang="hu">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>mozdIT — Felhasználói útmutató</title>
<style>
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; background: #0f1117; color: #e2e8f0; line-height: 1.65; padding-bottom: 64px; }
header { background: linear-gradient(135deg,#1a1f2e,#252d40); border-bottom: 1px solid #2d3748; padding: 14px 32px; display: flex; align-items: center; gap: 12px; position: sticky; top: 0; z-index: 10; }
header h1 { font-size: 17px; font-weight: 700; background: linear-gradient(135deg,#60a5fa,#a78bfa); -webkit-background-clip: text; -webkit-text-fill-color: transparent; }
header a { color: #94a3b8; text-decoration: none; font-size: 14px; margin-left: auto; }
header a:hover { color: #e2e8f0; }
main { max-width: 760px; margin: 0 auto; padding: 32px 24px; }
h1 { font-size: 24px; margin: 16px 0 12px; color: #f1f5f9; }
h2 { font-size: 20px; margin: 28px 0 10px; color: #93c5fd; border-bottom: 1px solid #2d3748; padding-bottom: 6px; }
h3 { font-size: 16px; margin: 20px 0 8px; color: #c4b5fd; }
h4 { font-size: 14px; margin: 16px 0 6px; color: #c4b5fd; }
p { margin: 8px 0; }
ul, ol { margin: 8px 0 8px 22px; }
li { margin: 4px 0; }
a { color: #7dd3fc; }
code { background: #1e293b; border-radius: 4px; padding: 1px 6px; font-size: 0.9em; color: #fbbf24; }
pre { background: #1e293b; border: 1px solid #2d3748; border-radius: 8px; padding: 12px 16px; overflow-x: auto; margin: 12px 0; }
pre code { background: none; padding: 0; color: #e2e8f0; }
hr { border: none; border-top: 1px solid #2d3748; margin: 24px 0; }
</style>
</head>
<body>
<header>
<h1>mozdIT — Felhasználói útmutató</h1>
<a href="/">← Vissza a szerkesztőhöz</a>
</header>
<main>
${contentHtml}
</main>
</body>
</html>`;
// Login page — simple logo page shown after logout (and for unauthenticated browser visits).
const LOGIN_PAGE = () => `<!DOCTYPE html>
<html lang="hu">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>mozdIT CMS — Belépés</title>
<style>
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; background: #0f1117; color: #e2e8f0; min-height: 100vh; display: flex; align-items: center; justify-content: center; padding: 24px; }
.environment-banner { background: #f59e0b; color: #111827; padding: 9px 32px; text-align: center; font-size: 13px; font-weight: 800; letter-spacing: .08em; text-transform: uppercase; position: fixed; top: 0; left: 0; right: 0; }
.card { background: linear-gradient(160deg,#1a1f2e,#252d40); border: 1px solid #2d3748; border-radius: 16px; padding: 40px 36px; width: 100%; max-width: 380px; box-shadow: 0 20px 50px rgba(0,0,0,.45); }
.logo { text-align: center; margin-bottom: 28px; }
.logo img { height: 56px; }
h1 { font-size: 20px; font-weight: 700; text-align: center; margin-bottom: 4px; }
.subtitle { color: #94a3b8; font-size: 14px; text-align: center; margin-bottom: 26px; }
label { display: block; font-size: 13px; color: #94a3b8; margin: 14px 0 6px; }
input { width: 100%; background: #0f1117; border: 1px solid #2d3748; border-radius: 8px; color: #e2e8f0; padding: 11px 14px; font-size: 15px; }
input:focus { outline: none; border-color: #60a5fa; }
button { width: 100%; margin-top: 24px; background: linear-gradient(135deg,#3b82f6,#8b5cf6); color: #fff; border: none; border-radius: 8px; padding: 12px; font-size: 15px; font-weight: 700; cursor: pointer; }
button:hover { filter: brightness(1.1); }
button:disabled { opacity: .6; cursor: wait; }
.error { color: #f87171; font-size: 14px; text-align: center; margin-top: 14px; min-height: 20px; }
</style>
</head>
<body>
${isStaging() ? '<div class="environment-banner">⚠ STAGING / TESZTKÖRNYEZET</div>' : ''}
<div class="card">
<div class="logo"><img src="/logo.png" alt="mozdIT"></div>
<h1>Content Editor</h1>
<p class="subtitle">Belépés a tartalomszerkesztőbe</p>
<form onsubmit="return login(event)">
<label for="user">Felhasználónév</label>
<input id="user" name="user" autocomplete="username" autofocus required>
<label for="pass">Jelszó</label>
<input id="pass" name="pass" type="password" autocomplete="current-password" required>
<button type="submit" id="btn">Belépés</button>
</form>
<p class="error" id="err"></p>
</div>
<script>
async function login(e) {
e.preventDefault();
const btn = document.getElementById('btn');
const err = document.getElementById('err');
btn.disabled = true; err.textContent = '';
try {
const res = await fetch('/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ user: document.getElementById('user').value, pass: document.getElementById('pass').value })
});
if (res.ok) { location.href = '/'; return; }
const json = await res.json().catch(() => ({}));
err.textContent = json.error || 'Sikertelen belépés — ellenőrizd a felhasználónevet és a jelszót.';
} catch (e2) {
err.textContent = 'Hálózati hiba — próbáld újra.';
}
btn.disabled = false;
}
</script>
</body>
</html>`;
module.exports = { HTML, GUIDE_PAGE, LOGIN_PAGE };