Фронтенд: стабильность прокси и рендера
- main.py: таймаут прокси 150с (холодная загрузка бэкенда до ~2мин), /healthz, чистый список маршрутов - app.js: таймаут fetch (AbortController), защита от гонок при переключении вкладок (requestSeq) - style.css: стили chипа ошибки кэша (multi-line заметки уже поддерживались)
This commit is contained in:
@@ -19,6 +19,8 @@ const PARITY_CLASS = { ч: "badge badge-ch", з: "badge badge-zn" };
|
||||
|
||||
let currentParity = null;
|
||||
let mode = "groups";
|
||||
let requestSeq = 0;
|
||||
const FETCH_TIMEOUT_MS = 30000;
|
||||
|
||||
function todayName() {
|
||||
return RUSSIAN_DAYS[new Date().getDay()];
|
||||
@@ -29,7 +31,16 @@ function enc(value) {
|
||||
}
|
||||
|
||||
async function fetchJSON(url) {
|
||||
const resp = await fetch(url);
|
||||
const controller = new AbortController();
|
||||
const timer = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);
|
||||
let resp;
|
||||
try {
|
||||
resp = await fetch(url, { signal: controller.signal });
|
||||
} catch (err) {
|
||||
throw new Error(err.name === "AbortError" ? "Время запроса истекло" : err.message);
|
||||
} finally {
|
||||
clearTimeout(timer);
|
||||
}
|
||||
if (!resp.ok) {
|
||||
let detail = resp.statusText;
|
||||
try {
|
||||
@@ -44,7 +55,7 @@ async function fetchJSON(url) {
|
||||
async function loadWeek() {
|
||||
try {
|
||||
const data = await fetchJSON("/api/week");
|
||||
currentParity = data.parity;
|
||||
if (data) currentParity = data.parity;
|
||||
} catch (_) {}
|
||||
}
|
||||
|
||||
@@ -104,32 +115,37 @@ async function setMode(next) {
|
||||
await renderView();
|
||||
}
|
||||
|
||||
async function renderView(force = false) {
|
||||
async function renderView() {
|
||||
const value = groupSelect.value;
|
||||
if (!value) return;
|
||||
localStorage.setItem(MODES[mode].valueKey, value);
|
||||
const seq = ++requestSeq;
|
||||
scheduleEl.innerHTML = "";
|
||||
errorEl.classList.add("hidden");
|
||||
loadingEl.classList.remove("hidden");
|
||||
try {
|
||||
let data, status;
|
||||
if (mode === "groups") {
|
||||
const [data, status] = await Promise.all([
|
||||
fetchJSON(`/api/schedule/${enc(value)}${force ? "?force=1" : ""}`),
|
||||
[data, status] = await Promise.all([
|
||||
fetchJSON(`/api/schedule/${enc(value)}`),
|
||||
loadStatus(),
|
||||
]);
|
||||
if (seq !== requestSeq) return;
|
||||
renderGroupSchedule(data, status);
|
||||
} else {
|
||||
const [data, status] = await Promise.all([
|
||||
[data, status] = await Promise.all([
|
||||
fetchJSON(`/api/teacher/${enc(value)}`),
|
||||
loadStatus(),
|
||||
]);
|
||||
if (seq !== requestSeq) return;
|
||||
renderTeacher(data, status);
|
||||
}
|
||||
} catch (err) {
|
||||
if (seq !== requestSeq) return;
|
||||
errorEl.textContent = "Ошибка: " + err.message;
|
||||
errorEl.classList.remove("hidden");
|
||||
} finally {
|
||||
loadingEl.classList.add("hidden");
|
||||
if (seq === requestSeq) loadingEl.classList.add("hidden");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -157,6 +173,11 @@ function statusRowHTML(status) {
|
||||
const synced = formatDT(status.last_updated);
|
||||
if (modified) row.appendChild(statusChip("Изменение таблицы", modified));
|
||||
if (synced) row.appendChild(statusChip("Синхронизация", synced));
|
||||
if (status.error) {
|
||||
const chip = statusChip("Ошибка", status.error);
|
||||
chip.classList.add("error");
|
||||
row.appendChild(chip);
|
||||
}
|
||||
}
|
||||
if (currentParity) {
|
||||
const chip = document.createElement("div");
|
||||
@@ -371,11 +392,14 @@ const FREE_WEEKDAYS = ["понедельник", "вторник", "среда",
|
||||
|
||||
async function renderRoomsView(day) {
|
||||
const chosenDay = FREE_WEEKDAYS.includes(day) ? day : (FREE_WEEKDAYS.includes(todayName()) ? todayName() : "понедельник");
|
||||
const seq = ++requestSeq;
|
||||
scheduleEl.innerHTML = "";
|
||||
errorEl.classList.add("hidden");
|
||||
loadingEl.classList.remove("hidden");
|
||||
try {
|
||||
const status = await loadStatus();
|
||||
const data = await fetchJSON(`/api/rooms-day?day=${enc(chosenDay)}`);
|
||||
if (seq !== requestSeq) return;
|
||||
scheduleEl.appendChild(Object.assign(document.createElement("div"), {
|
||||
className: "schedule-title",
|
||||
textContent: "Занятость аудиторий",
|
||||
@@ -386,14 +410,13 @@ async function renderRoomsView(day) {
|
||||
const wrap = document.createElement("div");
|
||||
wrap.className = "rooms-wrap";
|
||||
scheduleEl.appendChild(wrap);
|
||||
|
||||
const data = await fetchJSON(`/api/rooms-day?day=${enc(chosenDay)}`);
|
||||
wrap.appendChild(roomsDayTable(data));
|
||||
} catch (err) {
|
||||
if (seq !== requestSeq) return;
|
||||
errorEl.textContent = "Ошибка: " + err.message;
|
||||
errorEl.classList.remove("hidden");
|
||||
} finally {
|
||||
loadingEl.classList.add("hidden");
|
||||
if (seq === requestSeq) loadingEl.classList.add("hidden");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user