← back to Robet Site

public/admin.js

65 lines

// ROBERT admin — API-key entry (externalized so CSP can stay strict, no inline script).
const $ = (s) => document.querySelector(s);
const api = (p, opt) => fetch(p, opt).then((r) => r.json());

async function load() {
  const rows = await api('/api/keys');
  const t = $('#table');
  if (!rows.length) { t.innerHTML = '<p class="mono" style="font-weight:600">No keys stored yet.</p>'; return; }
  t.innerHTML =
    '<div class="keyrow head"><div>Name</div><div>Value</div><div>Action</div></div>' +
    rows.map((r) =>
      `<div class="keyrow">
         <div class="mono">${r.name}</div>
         <div class="mono">${r.masked}</div>
         <div><button class="btn warn" data-del="${r.name}">Delete</button></div>
       </div>`).join('');
  t.querySelectorAll('[data-del]').forEach((b) =>
    b.addEventListener('click', async () => {
      await api('/api/keys/' + encodeURIComponent(b.dataset.del), { method: 'DELETE' });
      load();
    }));
}

// ── show/hide value toggle ──
const toggle = $('#toggleVal'), kVal = $('#kVal');
toggle.addEventListener('click', () => {
  const show = kVal.type === 'password';
  kVal.type = show ? 'text' : 'password';
  toggle.textContent = show ? 'hide' : 'show';
  toggle.setAttribute('aria-pressed', String(show));
  toggle.setAttribute('aria-label', show ? 'Hide value' : 'Show value');
  kVal.focus();
});

// ── inline validation (live) ──
const NAME_RE = /^[A-Za-z0-9_]+$/;
const hint = $('#hint');
function validate() {
  const name = $('#kName').value.trim(), value = kVal.value;
  let ok = true, m = 'Name: letters, digits, underscores only. Value required.';
  if (name && !NAME_RE.test(name)) { ok = false; m = '✗ Name allows only letters, digits, underscores (no spaces).'; }
  else if (!name || !value) { ok = false; }
  $('#saveBtn').disabled = !ok;
  hint.textContent = m;
  hint.style.color = (name && !NAME_RE.test(name)) ? 'var(--accent2)' : '';
  return ok;
}
$('#kName').addEventListener('input', validate);
kVal.addEventListener('input', validate);

$('#saveBtn').addEventListener('click', async () => {
  const name = $('#kName').value.trim(), value = kVal.value;
  const msg = $('#msg');
  if (!validate()) { msg.textContent = '✗ Fix the highlighted field'; return; }
  const j = await api('/api/keys', {
    method: 'POST', headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ name, value }),
  });
  msg.textContent = j.ok ? `✓ Saved ${j.name} (${j.masked})` : '✗ ' + (j.error || 'Failed');
  if (j.ok) { $('#kName').value = ''; kVal.value = ''; validate(); load(); }
});

validate();
load();