templates
This commit is contained in:
@@ -30,6 +30,18 @@
|
||||
.logo-row { display: flex; gap: 8px; align-items: center; margin-bottom: 12px; }
|
||||
.logo-row input { flex: 1; background: #2a2a2a; border: 1px solid #444; color: #eee; padding: 6px 10px; border-radius: 4px; font-size: 12px; }
|
||||
.logo-row label { font-size: 12px; color: #aaa; white-space: nowrap; }
|
||||
|
||||
.version-panel { background: #222; border: 1px solid #444; border-radius: 6px; padding: 16px; margin-bottom: 20px; }
|
||||
.version-list { margin-top: 10px; max-height: 200px; overflow-y: auto; }
|
||||
.version-item { display: flex; justify-content: space-between; align-items: center; padding: 8px; border-bottom: 1px solid #333; font-size: 13px; }
|
||||
.version-item:hover { background: #2a2a2a; }
|
||||
.version-info { flex: 1; cursor: pointer; }
|
||||
.version-name { font-weight: bold; color: #C9A84C; }
|
||||
.version-date { font-size: 11px; color: #777; margin-top: 2px; }
|
||||
.version-actions { display: flex; gap: 8px; }
|
||||
.v-btn { padding: 4px 8px; font-size: 11px; }
|
||||
.save-form { display: flex; gap: 8px; margin-top: 10px; }
|
||||
.save-form input { flex: 1; background: #333; border: 1px solid #444; color: #eee; padding: 8px; border-radius: 4px; font-size: 13px; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
@@ -43,6 +55,17 @@
|
||||
<main>
|
||||
<div class="layout">
|
||||
<div class="panel">
|
||||
<div class="version-panel">
|
||||
<h2>Saved Versions</h2>
|
||||
<div class="save-form">
|
||||
<input id="v-name" placeholder="Version Name (e.g. Holiday 2026)"/>
|
||||
<button onclick="saveVersion()" class="v-btn">Save As Version</button>
|
||||
</div>
|
||||
<div class="version-list" id="version-list">
|
||||
<div style="color:#777; padding: 10px;">Loading versions...</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h2>Handlebars Template (HTML)</h2>
|
||||
<textarea id="template-editor" spellcheck="false"></textarea>
|
||||
<div class="actions">
|
||||
@@ -80,9 +103,67 @@
|
||||
async function saveTemplate() {
|
||||
const content = document.getElementById('template-editor').value;
|
||||
const res = await fetch('/api/admin/template',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({content})}).then(r=>r.json());
|
||||
showStatus(res.ok ? 'Template saved! A .bak backup was created.' : 'Error: '+res.error, res.ok);
|
||||
showStatus(res.ok ? 'Template deployed! A .bak backup was created.' : 'Error: '+res.error, res.ok);
|
||||
updatePreview();
|
||||
}
|
||||
|
||||
async function loadVersions() {
|
||||
const versions = await fetch('/api/admin/versions').then(r=>r.json()).catch(()=>[]);
|
||||
const list = document.getElementById('version-list');
|
||||
if (!versions.length) {
|
||||
list.innerHTML = '<div style="color:#777; padding:10px;">No saved versions yet.</div>';
|
||||
return;
|
||||
}
|
||||
list.innerHTML = versions.map(v => `
|
||||
<div class="version-item">
|
||||
<div class="version-info" onclick="loadVersionData(${v.id})">
|
||||
<div class="version-name">${v.name}</div>
|
||||
<div class="version-date">${new Date(v.updated_at).toLocaleString()}</div>
|
||||
</div>
|
||||
<div class="version-actions">
|
||||
<button class="secondary v-btn" onclick="deployVersion(${v.id})">Deploy</button>
|
||||
</div>
|
||||
</div>
|
||||
`).join('');
|
||||
}
|
||||
|
||||
async function saveVersion() {
|
||||
const name = document.getElementById('v-name').value.trim();
|
||||
const content = document.getElementById('template-editor').value;
|
||||
if (!name) return alert('Enter a version name.');
|
||||
|
||||
const res = await fetch('/api/admin/versions', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ name, content })
|
||||
}).then(r=>r.json());
|
||||
|
||||
if (res.ok) {
|
||||
showStatus('Version saved!', true);
|
||||
document.getElementById('v-name').value = '';
|
||||
loadVersions();
|
||||
} else {
|
||||
showStatus('Error: '+res.error, false);
|
||||
}
|
||||
}
|
||||
|
||||
async function loadVersionData(id) {
|
||||
const v = await fetch(`/api/admin/versions/${id}`).then(r=>r.json());
|
||||
document.getElementById('template-editor').value = v.content;
|
||||
document.getElementById('v-name').value = v.name;
|
||||
updatePreview();
|
||||
}
|
||||
|
||||
async function deployVersion(id) {
|
||||
if (!confirm('Set this version as the ACTIVE template?')) return;
|
||||
const res = await fetch(`/api/admin/versions/${id}/deploy`, { method: 'POST' }).then(r=>r.json());
|
||||
if (res.ok) {
|
||||
showStatus('Version deployed and active!', true);
|
||||
} else {
|
||||
showStatus('Error: '+res.error, false);
|
||||
}
|
||||
}
|
||||
|
||||
async function updatePreview() {
|
||||
const templateHtml = document.getElementById('template-editor').value;
|
||||
const userData = {
|
||||
@@ -102,6 +183,7 @@
|
||||
setTimeout(()=>{ el.style.display='none'; }, 5000);
|
||||
}
|
||||
loadTemplate();
|
||||
loadVersions();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -30,6 +30,12 @@ function initDb() {
|
||||
custom_html TEXT,
|
||||
updated_at TEXT
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS templates (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name TEXT NOT NULL UNIQUE,
|
||||
content TEXT NOT NULL,
|
||||
updated_at TEXT NOT NULL
|
||||
);
|
||||
`);
|
||||
const existing = db.prepare("SELECT value FROM settings WHERE key='template_name'").get();
|
||||
if (!existing) {
|
||||
|
||||
34
src/db/templateDb.js
Normal file
34
src/db/templateDb.js
Normal file
@@ -0,0 +1,34 @@
|
||||
const { getDb } = require('./sqlite');
|
||||
|
||||
function saveTemplate(name, content) {
|
||||
const db = getDb();
|
||||
const timestamp = new Date().toISOString();
|
||||
|
||||
// Check if exists
|
||||
const existing = db.prepare('SELECT id FROM templates WHERE name = ?').get(name);
|
||||
|
||||
if (existing) {
|
||||
return db.prepare('UPDATE templates SET content = ?, updated_at = ? WHERE id = ?')
|
||||
.run(content, timestamp, existing.id);
|
||||
} else {
|
||||
return db.prepare('INSERT INTO templates (name, content, updated_at) VALUES (?, ?, ?)')
|
||||
.run(name, content, timestamp);
|
||||
}
|
||||
}
|
||||
|
||||
function getAllTemplateVersions() {
|
||||
const db = getDb();
|
||||
return db.prepare('SELECT id, name, updated_at FROM templates ORDER BY updated_at DESC').all();
|
||||
}
|
||||
|
||||
function getTemplateById(id) {
|
||||
const db = getDb();
|
||||
return db.prepare('SELECT * FROM templates WHERE id = ?').get(id);
|
||||
}
|
||||
|
||||
function deleteTemplate(id) {
|
||||
const db = getDb();
|
||||
return db.prepare('DELETE FROM templates WHERE id = ?').run(id);
|
||||
}
|
||||
|
||||
module.exports = { saveTemplate, getAllTemplateVersions, getTemplateById, deleteTemplate };
|
||||
@@ -4,6 +4,11 @@ const fs = require('fs');
|
||||
const path = require('path');
|
||||
const { getRecentLogs } = require('../db/audit');
|
||||
const { getAllUsers } = require('../services/googleAdmin');
|
||||
const {
|
||||
getAllTemplateVersions,
|
||||
getTemplateById,
|
||||
saveTemplate
|
||||
} = require('../db/templateDb');
|
||||
|
||||
router.get('/logs', (req, res) => {
|
||||
const logs = getRecentLogs(parseInt(req.query.limit) || 200);
|
||||
@@ -59,4 +64,51 @@ router.post('/preview', (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
router.get('/versions', (req, res) => {
|
||||
try {
|
||||
const versions = getAllTemplateVersions();
|
||||
res.json(versions);
|
||||
} catch (err) {
|
||||
res.status(500).json({ error: err.message });
|
||||
}
|
||||
});
|
||||
|
||||
router.get('/versions/:id', (req, res) => {
|
||||
try {
|
||||
const version = getTemplateById(req.params.id);
|
||||
if (!version) return res.status(404).json({ error: 'Not found' });
|
||||
res.json(version);
|
||||
} catch (err) {
|
||||
res.status(500).json({ error: err.message });
|
||||
}
|
||||
});
|
||||
|
||||
router.post('/versions', (req, res) => {
|
||||
const { name, content } = req.body;
|
||||
if (!name || !content) return res.status(400).json({ error: 'name and content required' });
|
||||
try {
|
||||
saveTemplate(name, content);
|
||||
res.json({ ok: true });
|
||||
} catch (err) {
|
||||
res.status(500).json({ error: err.message });
|
||||
}
|
||||
});
|
||||
|
||||
router.post('/versions/:id/deploy', (req, res) => {
|
||||
try {
|
||||
const version = getTemplateById(req.params.id);
|
||||
if (!version) return res.status(404).json({ error: 'Not found' });
|
||||
|
||||
const templatePath = path.join(__dirname, '../../templates/default.hbs');
|
||||
// Create backup
|
||||
if (fs.existsSync(templatePath)) {
|
||||
fs.writeFileSync(templatePath + '.bak', fs.readFileSync(templatePath));
|
||||
}
|
||||
fs.writeFileSync(templatePath, version.content);
|
||||
res.json({ ok: true });
|
||||
} catch (err) {
|
||||
res.status(500).json({ error: err.message });
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
|
||||
Reference in New Issue
Block a user