// The developer's home: a status card (is each part working, which version runs, when it was last updated), an activity chart // (how many people used the app on each of the last 7 or 30 days) and every permission (the other roles' chips open their screens). // The other tools (prompter, lab, AI cost) come later. import { api } from "./api.js"; import { esc } from "./math.js"; const STATES = { ok: { icon: "✓", word: "תקין" }, warn: { icon: "!", word: "שימו לב" }, error: { icon: "✕", word: "תקלה" }, off: { icon: "–", word: "כבוי" } }; const OVERALL = { ok: "המערכת תקינה", warn: "המערכת עובדת, עם הערות", error: "יש תקלה במערכת" }; const when = iso => iso ? new Intl.DateTimeFormat("he-IL", { day: "numeric", month: "short", year: "numeric", hour: "2-digit", minute: "2-digit", timeZone: "Asia/Jerusalem" }).format(new Date(iso)) : "לא ידוע"; function since(seconds) { const d = Math.floor(seconds / 86400), h = Math.floor(seconds % 86400 / 3600), m = Math.floor(seconds % 3600 / 60); return d ? `לפני ${d} ימים ו־${h} שעות` : h ? `לפני ${h} שעות ו־${m} דקות` : m ? `לפני ${m} דקות` : "לפני פחות מדקה"; } function card(s) { const v = s.version; return `
נבדק ב־${esc(when(s.now))} · Python ${esc(s.python)}
`; } let asked = 0; // only the latest check is drawn, if two overlap async function load() { const mine = ++asked; let html; try { html = card(await api("/api/dev/status")); } catch (error) { html = `לא הצלחנו לקרוא את מצב המערכת: ${esc(error.message || "")}
`; } const box = document.getElementById("st-body"); if (box && mine === asked) box.innerHTML = html; } // ---- activity: one column per Israel day, oldest on the left; the height is how many different people were active ---- const DAY = new Intl.DateTimeFormat("he-IL", { weekday: "short", timeZone: "UTC" }); const DATE = new Intl.DateTimeFormat("he-IL", { day: "numeric", month: "numeric", timeZone: "UTC" }); const LONG = new Intl.DateTimeFormat("he-IL", { weekday: "long", day: "numeric", month: "long", timeZone: "UTC" }); const at = iso => new Date(`${iso}T00:00:00Z`); // a calendar day, drawn as that day whatever the device's zone const people = n => n === 1 ? "משתמש אחד" : `${n.toLocaleString("he-IL")} משתמשים`; /** A clean top for the axis (1, 2 or 5 times a power of ten) and its step, about four gridlines. */ function scale(max) { if (max <= 4) return { top: Math.max(max, 1), step: 1 }; const rough = max / 4, power = 10 ** Math.floor(Math.log10(rough)); const step = [1, 2, 5, 10].map(m => m * power).find(s => s >= rough); return { top: Math.ceil(max / step) * step, step }; } function chart(a) { const days = a.days, n = days.length, { top, step } = scale(Math.max(...days.map(d => d.users))); const peak = days.reduce((best, d, i) => d.users > days[best].users ? i : best, 0); const every = n <= 7 ? 1 : 5; // which days get a label under the axis const ticks = []; for (let v = 0; v <= top; v += step) ticks.push(v); return `| יום | משתמשים | תשובות | בחנים ומבחנים |
|---|---|---|---|
| ${esc(LONG.format(at(d.day)))} | ${d.users} | ${d.answers} | ${d.attempts} |
משתמש פעיל: ענה על שאלה בתרגול או התחיל בוחן או מבחן באותו יום (לפי שעון ישראל).
`; } let range = 7, askedActivity = 0; async function loadActivity() { const mine = ++askedActivity; let html; try { html = activityCard(await api(`/api/dev/activity?days=${range}`)); } catch (error) { html = `לא הצלחנו לקרוא את הפעילות: ${esc(error.message || "")}
`; } const box = document.getElementById("ac-body"); if (box && mine === askedActivity) box.innerHTML = html; } export async function show(me, view) { view(`בודקים…
טוענים…
יש לך את כל ההרשאות: אפשר לפתוח את המסכים של כל התפקידים מהכפתורים למעלה.
עוד כלי מפתח (מעבדה, עלות ה־AI) יתווספו בשלבים הבאים.
${esc(me.email)}