/* © 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 && unlock(x.userId)}>Unlock }
))}
{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.")}
{ await secApi("/check", "POST", {}); load(); }}>Check again
{ setShowEv(!showEv); if (!events) loadEv(); }}>{showEv ? "Hide" : "Show"} security events
Export CSV
{showEv &&
{(events || []).map((ev) => (
{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 : "")} ))}
{events && !events.length && 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}
{ const v = b.level + ":" + b.daysLeft; try { sessionStorage.setItem("k2b-sec-banner-hidden", v); } catch (e) {} setHidden(v); }} style={{ position: "absolute", right: 8, top: 3, background: "transparent", border: 0, color: "inherit", fontSize: 16, cursor: "pointer" }}>×
);
};
(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 });