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();