Add files via upload
This commit is contained in:
120
public/dashboard.html
Normal file
120
public/dashboard.html
Normal file
@@ -0,0 +1,120 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||
<title>Signature Manager — Dashboard</title>
|
||||
<style>
|
||||
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
body { font-family: Arial, sans-serif; background: #1a1a1a; color: #eee; }
|
||||
header { background: #111; border-bottom: 2px solid #C9A84C; padding: 14px 24px; display: flex; align-items: center; gap: 16px; }
|
||||
header h1 { font-size: 18px; color: #C9A84C; }
|
||||
nav a { color: #ccc; text-decoration: none; margin-left: 16px; font-size: 14px; }
|
||||
nav a:hover { color: #C9A84C; }
|
||||
main { padding: 24px; max-width: 1100px; margin: auto; }
|
||||
.cards { display: flex; gap: 16px; margin-bottom: 24px; flex-wrap: wrap; }
|
||||
.card { background: #2a2a2a; border: 1px solid #444; border-radius: 8px; padding: 20px; flex: 1; min-width: 200px; }
|
||||
.card h2 { font-size: 13px; color: #aaa; margin-bottom: 8px; }
|
||||
.card .val { font-size: 28px; font-weight: bold; color: #C9A84C; }
|
||||
.actions { display: flex; gap: 12px; margin-bottom: 24px; flex-wrap: wrap; align-items: center; }
|
||||
button { background: #C9A84C; color: #111; border: none; padding: 10px 20px; border-radius: 6px; font-weight: bold; cursor: pointer; font-size: 14px; }
|
||||
button:hover { background: #e0bc5c; }
|
||||
button.secondary { background: #333; color: #eee; border: 1px solid #555; }
|
||||
button.secondary:hover { background: #444; }
|
||||
table { width: 100%; border-collapse: collapse; font-size: 13px; }
|
||||
th { text-align: left; padding: 10px 12px; background: #222; color: #C9A84C; border-bottom: 2px solid #C9A84C; }
|
||||
td { padding: 8px 12px; border-bottom: 1px solid #333; vertical-align: top; }
|
||||
tr:hover td { background: #2a2a2a; }
|
||||
.badge { display: inline-block; padding: 2px 8px; border-radius: 4px; font-size: 11px; font-weight: bold; }
|
||||
.badge.success { background: #1a4a1a; color: #4caf50; }
|
||||
.badge.error { background: #4a1a1a; color: #f44336; }
|
||||
.badge.skipped { background: #3a3a1a; color: #aaa; }
|
||||
#status-msg { margin-bottom: 12px; padding: 10px 14px; border-radius: 6px; display: none; }
|
||||
#status-msg.ok { background: #1a4a1a; color: #4caf50; display: block; }
|
||||
#status-msg.err { background: #4a1a1a; color: #f44336; display: block; }
|
||||
input[type=email] { padding: 9px 12px; border-radius: 6px; border: 1px solid #555; background: #2a2a2a; color: #eee; font-size: 13px; width: 240px; }
|
||||
.ts { color: #777; font-size: 11px; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<h1>✉ Email Signature Manager</h1>
|
||||
<nav>
|
||||
<a href="/dashboard">Dashboard</a>
|
||||
<a href="/editor">Template Editor</a>
|
||||
</nav>
|
||||
</header>
|
||||
<main>
|
||||
<div class="cards">
|
||||
<div class="card"><h2>Total Users</h2><div class="val" id="stat-users">—</div></div>
|
||||
<div class="card"><h2>Last Push</h2><div class="val" id="stat-last" style="font-size:14px;margin-top:6px;">—</div></div>
|
||||
<div class="card"><h2>Last Run Success</h2><div class="val" id="stat-success">—</div></div>
|
||||
<div class="card"><h2>Last Run Errors</h2><div class="val" id="stat-errors">—</div></div>
|
||||
</div>
|
||||
<div class="actions">
|
||||
<button onclick="pushAll()">▶ Push to All Users</button>
|
||||
<button class="secondary" onclick="loadLogs()">↻ Refresh Logs</button>
|
||||
<input id="single-email" type="email" placeholder="user@domain.com" />
|
||||
<button class="secondary" onclick="pushSingle()">Push Single User</button>
|
||||
</div>
|
||||
<div id="status-msg"></div>
|
||||
<table>
|
||||
<thead><tr><th>Timestamp</th><th>User</th><th>Name</th><th>Status</th><th>Message</th></tr></thead>
|
||||
<tbody id="log-body"><tr><td colspan="5" style="color:#777;padding:20px;">Loading...</td></tr></tbody>
|
||||
</table>
|
||||
</main>
|
||||
<script>
|
||||
async function loadStats() {
|
||||
const users = await fetch('/api/admin/users').then(r=>r.json()).catch(()=>[]);
|
||||
document.getElementById('stat-users').textContent = Array.isArray(users) ? users.length : '—';
|
||||
const logs = await fetch('/api/admin/logs?limit=500').then(r=>r.json()).catch(()=>[]);
|
||||
if (logs.length) {
|
||||
document.getElementById('stat-last').textContent = new Date(logs[0].timestamp).toLocaleString();
|
||||
const recent = logs.slice(0,50);
|
||||
document.getElementById('stat-success').textContent = recent.filter(l=>l.status==='success').length;
|
||||
document.getElementById('stat-errors').textContent = recent.filter(l=>l.status==='error').length;
|
||||
}
|
||||
}
|
||||
async function loadLogs() {
|
||||
const logs = await fetch('/api/admin/logs?limit=200').then(r=>r.json()).catch(()=>[]);
|
||||
const tbody = document.getElementById('log-body');
|
||||
if (!logs.length) { tbody.innerHTML='<tr><td colspan="5" style="color:#777;">No logs yet.</td></tr>'; return; }
|
||||
tbody.innerHTML = logs.map(l=>`
|
||||
<tr>
|
||||
<td class="ts">${new Date(l.timestamp).toLocaleString()}</td>
|
||||
<td>${l.user_email}</td>
|
||||
<td>${l.display_name||''}</td>
|
||||
<td><span class="badge ${l.status}">${l.status}</span></td>
|
||||
<td style="color:#888;font-size:12px;">${l.message||''}</td>
|
||||
</tr>`).join('');
|
||||
}
|
||||
function showStatus(msg, ok) {
|
||||
const el = document.getElementById('status-msg');
|
||||
el.textContent = msg; el.className = ok ? 'ok' : 'err';
|
||||
setTimeout(()=>{ el.className=''; el.textContent=''; }, 7000);
|
||||
}
|
||||
async function pushAll() {
|
||||
if (!confirm('Push signatures to ALL users?')) return;
|
||||
showStatus('Pushing to all users... this may take a minute.', true);
|
||||
const res = await fetch('/api/push/all',{method:'POST'}).then(r=>r.json()).catch(e=>({ok:false,error:e.message}));
|
||||
if (res.ok) {
|
||||
const s=res.results.filter(r=>r.status==='success').length;
|
||||
const e=res.results.filter(r=>r.status==='error').length;
|
||||
showStatus(`Done! ${s} success, ${e} errors.`, e===0);
|
||||
} else { showStatus('Error: '+res.error, false); }
|
||||
loadLogs(); loadStats();
|
||||
}
|
||||
async function pushSingle() {
|
||||
const email = document.getElementById('single-email').value.trim();
|
||||
if (!email) return alert('Enter a user email first.');
|
||||
showStatus(`Pushing to ${email}...`, true);
|
||||
const res = await fetch('/api/push/user',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({email})}).then(r=>r.json()).catch(e=>({ok:false,error:e.message}));
|
||||
if (res.ok && res.results[0]?.status==='success') {
|
||||
showStatus(`Success: signature pushed to ${email}`, true);
|
||||
} else { showStatus('Error: '+(res.error||res.results?.[0]?.message||'unknown'), false); }
|
||||
loadLogs();
|
||||
}
|
||||
loadStats(); loadLogs();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
99
public/editor.html
Normal file
99
public/editor.html
Normal file
@@ -0,0 +1,99 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||
<title>Signature Manager — Template Editor</title>
|
||||
<style>
|
||||
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
body { font-family: Arial, sans-serif; background: #1a1a1a; color: #eee; }
|
||||
header { background: #111; border-bottom: 2px solid #C9A84C; padding: 14px 24px; display: flex; align-items: center; gap: 16px; }
|
||||
header h1 { font-size: 18px; color: #C9A84C; }
|
||||
nav a { color: #ccc; text-decoration: none; margin-left: 16px; font-size: 14px; }
|
||||
nav a:hover { color: #C9A84C; }
|
||||
main { padding: 24px; max-width: 1200px; margin: auto; }
|
||||
.layout { display: flex; gap: 20px; flex-wrap: wrap; }
|
||||
.panel { flex: 1; min-width: 300px; }
|
||||
.panel h2 { font-size: 14px; color: #C9A84C; margin-bottom: 10px; }
|
||||
textarea { width: 100%; height: 360px; background: #222; color: #eee; border: 1px solid #444; border-radius: 6px; padding: 12px; font-family: monospace; font-size: 12px; resize: vertical; }
|
||||
.preview-box { background: #fff; border-radius: 6px; padding: 20px; min-height: 120px; border: 1px solid #444; }
|
||||
.actions { display: flex; gap: 10px; margin-top: 16px; flex-wrap: wrap; }
|
||||
button { background: #C9A84C; color: #111; border: none; padding: 10px 20px; border-radius: 6px; font-weight: bold; cursor: pointer; font-size: 14px; }
|
||||
button:hover { background: #e0bc5c; }
|
||||
button.secondary { background: #333; color: #eee; border: 1px solid #555; }
|
||||
button.secondary:hover { background: #444; }
|
||||
#status-msg { margin-top: 12px; padding: 10px 14px; border-radius: 6px; display: none; }
|
||||
#status-msg.ok { background: #1a4a1a; color: #4caf50; display: block; }
|
||||
#status-msg.err { background: #4a1a1a; color: #f44336; display: block; }
|
||||
.test-fields { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 12px; }
|
||||
.test-fields input { background: #2a2a2a; border: 1px solid #444; color: #eee; padding: 6px 10px; border-radius: 4px; font-size: 12px; width: calc(50% - 4px); }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<h1>✉ Email Signature Manager</h1>
|
||||
<nav>
|
||||
<a href="/dashboard">Dashboard</a>
|
||||
<a href="/editor">Template Editor</a>
|
||||
</nav>
|
||||
</header>
|
||||
<main>
|
||||
<div class="layout">
|
||||
<div class="panel">
|
||||
<h2>Handlebars Template (HTML)</h2>
|
||||
<textarea id="template-editor" spellcheck="false"></textarea>
|
||||
<div class="actions">
|
||||
<button onclick="saveTemplate()">💾 Save Template</button>
|
||||
<button class="secondary" onclick="loadTemplate()">↻ Reset</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel">
|
||||
<h2>Live Preview</h2>
|
||||
<div class="test-fields">
|
||||
<input id="p-name" placeholder="Full Name" value="Jason Stedwell" oninput="updatePreview()"/>
|
||||
<input id="p-title" placeholder="Job Title" value="Director of Technical Services" oninput="updatePreview()"/>
|
||||
<input id="p-email" placeholder="Email" value="jstedwell@messagepointmedia.com" oninput="updatePreview()"/>
|
||||
<input id="p-phone" placeholder="Office Phone" value="334-707-2550" oninput="updatePreview()"/>
|
||||
<input id="p-cell" placeholder="Cell (optional)" value="" oninput="updatePreview()"/>
|
||||
</div>
|
||||
<div class="preview-box" id="preview-frame">Loading preview...</div>
|
||||
<div class="actions">
|
||||
<button class="secondary" onclick="updatePreview()">↻ Refresh Preview</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="status-msg"></div>
|
||||
</main>
|
||||
<script>
|
||||
async function loadTemplate() {
|
||||
const res = await fetch('/api/admin/template').then(r=>r.json());
|
||||
document.getElementById('template-editor').value = res.content;
|
||||
updatePreview();
|
||||
}
|
||||
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);
|
||||
updatePreview();
|
||||
}
|
||||
async function updatePreview() {
|
||||
const templateHtml = document.getElementById('template-editor').value;
|
||||
const userData = {
|
||||
fullName: document.getElementById('p-name').value,
|
||||
title: document.getElementById('p-title').value,
|
||||
email: document.getElementById('p-email').value,
|
||||
phone: document.getElementById('p-phone').value,
|
||||
cellPhone: document.getElementById('p-cell').value
|
||||
};
|
||||
const res = await fetch('/api/admin/preview',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({templateHtml,userData})}).then(r=>r.json()).catch(()=>({error:'Preview failed'}));
|
||||
document.getElementById('preview-frame').innerHTML = res.html || `<span style="color:red">${res.error}</span>`;
|
||||
}
|
||||
function showStatus(msg, ok) {
|
||||
const el = document.getElementById('status-msg');
|
||||
el.textContent = msg; el.className = ok ? 'ok' : 'err';
|
||||
setTimeout(()=>{ el.className=''; el.textContent=''; }, 5000);
|
||||
}
|
||||
loadTemplate();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user