const { useState: useStateA, useEffect: useEffectA, useMemo: useMemoA } = React; function useAuth() { const [user, setUser] = useStateA(null); const [checked, setChecked] = useStateA(false); useEffectA(() => { fetch('/api/auth/me') .then((r) => { if (r.ok) return r.json(); if (r.status === 404 || r.status === 501) return { user: "dev", __dev: true }; return null; }) .then((d) => { const u = d ? d.user : ""; setUser(u); if (u && !d.__dev) { Live.bootstrap(); Live.connect(); } }) .catch(() => setUser("dev")) .finally(() => setChecked(true)); }, []); return { user, checked, setUser }; } function App() { const { user, checked, setUser } = useAuth(); if (!checked) { return (
); } if (!user) { return { setUser(u); Live.bootstrap(); Live.connect(); }} />; } return setUser("")} />; } function AuthedApp({ user, onLogout }) { const handleLogout = async () => { await fetch('/api/auth/logout', { method: 'POST' }); onLogout(); }; const instances = useLiveInstances(); const liveStatus = useLiveStatus(); const [toasts, dismissToast] = useToasts(); const [extraCodes, setExtraCodes] = useStateA([]); const codes = useMemoA(() => { const set = new Set(extraCodes); instances.forEach((i) => { if (i.code) set.add(i.code); }); return [...set]; }, [instances, extraCodes]); const [selected, setSelected] = useStateA(new Set()); const [filterCode, setFilterCode] = useStateA(null); const [filterStatus, setFilterStatus] = useStateA(null); const [screen, setScreen] = useStateA("scheduler"); const [confirm, setConfirm] = useStateA(null); const [tzSync, setTzSync] = useStateA(false); const colorForCode = useMemoA(() => makeColorLookup(codes), [codes]); const codeFolders = []; const distinctCodes = codes.length; const onLaunch = (id) => Live.control([id], "launch"); const onStop = (id) => Live.control([id], "stop"); const onReset = (id) => setConfirm({ title: "Reset устройства", message: `Сбросить устройство [${id}]? Будет полная переинициализация VMOS (данные стираются, IP меняется).`, confirmLabel: "Reset", danger: true, onConfirm: () => Live.control([id], "reset"), }); const onSelectFolder = (code) => { setScreen("instances"); setFilterCode(code); }; const onSidebarChange = (key) => { setScreen(key); setFilterCode(null); }; const onCreateCode = (name) => setExtraCodes(prev => prev.includes(name) ? prev : [...prev, name]); const askConfirm = (opts) => setConfirm(opts); return (
{screen === "scheduler" && ( )} {screen === "instances" && ( )} {screen === "refresher" && } {screen === "codes" && } {screen === "stats" && } setConfirm(null)} onConfirm={() => confirm?.onConfirm?.()} title={confirm?.title} message={confirm?.message} summary={confirm?.summary} confirmLabel={confirm?.confirmLabel} danger={confirm?.danger} />
); } function ToastStack({ toasts, onDismiss }) { if (!toasts.length) return null; const color = (lvl) => lvl === "bad" ? "var(--bad)" : lvl === "ok" ? "var(--ok)" : "var(--warn)"; return (
{toasts.map((t) => (
{t.message}
{t.pads && t.pads.length > 0 && (
{t.pads.join(", ")}
)}
))}
); } const root = ReactDOM.createRoot(document.getElementById('root')); root.render();