/* © 2026 K2B NETWORK SOLUTIONS LLC. All rights reserved. Proprietary and confidential. Unauthorized copying, distribution, or use of this file, via any medium, is strictly prohibited. Original work product. */ /* eslint-disable */ // K2B DMS — RC36 (security, lane sec36): the owner's SECURITY card (Owner Panel) and the certificate banner every screen shows. // OwnSecurity [data-sec="card"] green / yellow / red lines: the site certificate (days left, renewal), the nightly backup + the weekly // restore test, free disk, failed sign-ins in 24 h + the logins waiting now (Unlock — needs a fresh code), // logins without a sign-in code, how many Admins, the last security check. Never a key, token or code. // [data-sec="events"] the store's security events (sign-ins, account changes, wipes, payroll, alerts) + Export CSV. // K2BSecBanner a thin bar at the bottom of every screen when the certificate runs out within 21 days (red within 7) — any signed-in login. // The SERVER decides everything (src/routes/security.js: /api/owner/security, /unlock, /events, /banner). const SEC_COLOR = { green: "#1E7B34", yellow: "#9A6A00", red: "#A02234" }; const SEC_BG = { green: "#EFFAF0", yellow: "#FFF8E5", red: "#FFF0F1" }; const secApi = async (path, method, body) => { try { const d = await apiFetch("/owner/security" + path, { method: method || "GET", body, noOwnerOverride: true }); return { ok: true, data: d || {} }; } catch (e) { return { ok: false, status: e.status || 0, code: e.body && e.body.code, error: (e.body && e.body.error) || e.message || "Server unreachable" }; } }; const secWhen = (d) => d ? new Date(d).toLocaleString([], { month: "short", day: "numeric", hour: "numeric", minute: "2-digit" }) : "—"; const SecLine = ({ level, label, children, tag }) => (
{label} {children}
); const OwnSecurity = ({ fresh }) => { const [st, setSt] = React.useState(null); const [events, setEvents] = React.useState(null); const [showEv, setShowEv] = React.useState(false); const load = async () => { const r = await secApi(""); setSt(r.ok ? r.data : { ok: false, error: r.error }); }; const loadEv = async () => { const r = await secApi("/events?limit=100"); setEvents(r.ok ? (r.data.events || []) : []); }; React.useEffect(() => { load(); }, []); const unlock = async (login) => { const r = await secApi("/unlock", "POST", { login }); window.k2bToast && window.k2bToast(r.ok ? "Login " + login + " can sign in again now." : r.error, r.ok ? "ok" : "warn", 4500); load(); }; const csv = async () => { try { const blob = await dmsApiBlob("/owner/security/events?format=csv&limit=5000"); // DMSCore: the same token / store headers as every request const a = document.createElement("a"); a.href = URL.createObjectURL(blob); a.download = "security-events.csv"; document.body.appendChild(a); a.click(); setTimeout(() => { URL.revokeObjectURL(a.href); a.remove(); }, 500); } catch (e) { window.k2bToast && window.k2bToast("The export could not be made: " + e.message, "warn"); } }; const Sect = typeof OwnSection === "function" ? OwnSection : (({ title, children }) =>
{title}{children}
); // OwnerPanel.jsx.txt's card frame const TD = typeof OWN_TD === "object" ? OWN_TD : { fontSize: 12.5, padding: "6px 8px", borderBottom: "1px solid #F0F1F4" }; if (!st) return
Loading…
; if (st.ok === false) return
⚠ {st.error}
; const c = st.cert || {}, b = st.backup || {}, d = st.disk || {}, s = st.signin || {}, l = st.logins || {}, k = st.checkScript || {}, rn = st.renewal || {}; const overall = st.level || "yellow"; return (
{c.daysLeft === null || c.daysLeft === undefined ? (c.note || c.error || "Not checked yet (the server checks once a day).") : (c.daysLeft < 0 ? "EXPIRED on " : c.daysLeft + " days left — runs out ") + String(c.validTo || "").slice(0, 10) + (c.issuer ? " (" + c.issuer + ")" : "")} {rn.state && rn.state !== "active" ? · automatic renewal: {rn.state} : rn.state === "active" ? · renews by itself : null} {b.backup ? <>Last copy {secWhen(b.backup.at)} — {b.backup.ok ? "OK" : "FAILED"}{b.backup.ok ? (b.backup.encrypted ? ", encrypted" : ", NOT encrypted (set a backup passphrase)") : ""} : "No nightly backup yet."} {b.restoreTest ? <> · last restore test {secWhen(b.restoreTest.at)} — {b.restoreTest.ok ? "OK (row counts match)" : "FAILED"} : <> · no restore test yet} {b.note ?
{b.note}
: null}
{d.freePct !== undefined ? d.freePct + " % free (" + d.freeGb + " GB)" : (d.note || "unknown")} {s.failed24h || 0} wrong password(s) in the last 24 hours{(s.waiting || []).some((x) => x.waiting) ? "" : " · no login is waiting"} {(s.waiting || []).filter((x) => x.waiting).map((x) => (
⏳ {x.login} waits {Math.ceil((x.retryInSec || 0) / 60)} more minute(s) {x.userId && }
))}
{l.active} active · {l.admins} Admin{l.admins === 1 ? "" : "s"} · {l.withoutCode} without a sign-in code {k.at ? "Last run " + k.at + (k.summary ? " — " + k.summary : "") : (k.note || "Not run yet.")}
{showEv &&
{(events || []).map((ev) => ( ))} {events && !events.length && }
{secWhen(ev.at)}{ev.actor_name || ev.actor_id}{ev.action}{ev.target_user ? " — " + ev.target_user : (ev.detail && ev.detail.login ? " — " + ev.detail.login : "")}
No security events yet.
}
); }; /* the certificate banner: a thin bar at the bottom of every screen (any signed-in login), hidden for this browser tab with × */ const K2BSecBanner = () => { const [b, setB] = React.useState(null); const [hidden, setHidden] = React.useState(() => { try { return sessionStorage.getItem("k2b-sec-banner-hidden") || ""; } catch (e) { return ""; } }); React.useEffect(() => { let alive = true; const poll = async () => { try { const r = await apiFetch("/owner/security/banner", { noOwnerOverride: true }); if (alive) setB(r && r.banner ? r.banner : null); } catch (e) { /* signed out / older server: no banner */ } }; const t1 = setTimeout(poll, 8000), t2 = setInterval(poll, 30 * 60 * 1000); return () => { alive = false; clearTimeout(t1); clearInterval(t2); }; }, []); if (!b || hidden === b.level + ":" + b.daysLeft) return null; return (
🔒 {b.text}
); }; (function mountSecBanner() { // RC36 merge (rc36m): never on a CUSTOMER's signing page (?rosign= / ?dealsign=, e.g. signed at the counter PC where a staff login is // still stored): the staff security banner is not for customers, and its React root interleaved with that page's first render if (/[?&](rosign|dealsign)=/.test(location.search || "")) return; const go = () => { try { if (document.getElementById("k2b-sec-banner")) return; const el = document.createElement("div"); el.id = "k2b-sec-banner"; document.body.appendChild(el); ReactDOM.createRoot(el).render(); } catch (e) { /* the banner is a convenience only */ } }; if (document.readyState === "loading") document.addEventListener("DOMContentLoaded", go); else go(); })(); Object.assign(window, { OwnSecurity, K2BSecBanner });