fix UI 12

This commit is contained in:
TronoSfera 2026-03-10 22:37:34 +03:00
parent 3bff88b38a
commit 253e7d5839
4 changed files with 76 additions and 2 deletions

View file

@ -2105,6 +2105,7 @@
// app/web/admin/shared/constants.js
var LS_TOKEN = "admin_access_token";
var ADMIN_AUTH_REDIRECT_REASON_KEY = "admin_auth_redirect_reason";
var PAGE_SIZE = 50;
var DEFAULT_FORM_FIELD_TYPES = ["string", "text", "number", "boolean", "date"];
var ALL_OPERATORS = ["=", "!=", ">", "<", ">=", "<=", "~"];
@ -5843,6 +5844,21 @@
}
if (!response.ok) {
const message = payload && (payload.detail || payload.error || payload.raw) || "HTTP " + response.status;
if (response.status === 401 && opts.auth !== false) {
try {
localStorage.removeItem(LS_TOKEN);
sessionStorage.setItem(ADMIN_AUTH_REDIRECT_REASON_KEY, "expired");
} catch (_) {
}
if (typeof window !== "undefined") {
const target = "/admin.html";
if (window.location.pathname !== target || window.location.search) {
window.location.replace(target);
} else {
window.location.reload();
}
}
}
const error = new Error(translateApiError(String(message)));
error.httpStatus = Number(response.status || 0);
throw error;
@ -7627,6 +7643,13 @@
setStatusMap((prev) => ({ ...prev, [key]: { message: message || "", kind: kind || "" } }));
}, []);
const getStatus = useCallback((key) => statusMap[key] || { message: "", kind: "" }, [statusMap]);
const isAdminTokenExpired = useCallback((rawToken) => {
const payload = decodeJwtPayload(rawToken || "");
const exp = Number((payload == null ? void 0 : payload.exp) || 0);
if (!payload || !payload.role || !payload.email) return true;
if (!Number.isFinite(exp) || exp <= 0) return true;
return exp * 1e3 <= Date.now();
}, []);
const api = useAdminApi(token);
const {
requestModal,
@ -9657,6 +9680,7 @@
const nextToken = data.access_token;
const payload = decodeJwtPayload(nextToken || "");
if (!payload || !payload.role || !payload.email) throw new Error("\u041D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u043F\u0440\u043E\u0447\u0438\u0442\u0430\u0442\u044C \u0434\u0430\u043D\u043D\u044B\u0435 \u0442\u043E\u043A\u0435\u043D\u0430");
sessionStorage.removeItem(ADMIN_AUTH_REDIRECT_REASON_KEY);
localStorage.setItem(LS_TOKEN, nextToken);
setToken(nextToken);
setRole(payload.role);
@ -9674,18 +9698,29 @@
[api, bootstrapReferenceData, loadDashboard, loadTotpStatus, setStatus]
);
useEffect(() => {
const authRedirectReason = sessionStorage.getItem(ADMIN_AUTH_REDIRECT_REASON_KEY) || "";
if (authRedirectReason === "expired") {
setStatus("login", "\u0421\u0435\u0441\u0441\u0438\u044F \u0438\u0441\u0442\u0435\u043A\u043B\u0430. \u0412\u043E\u0439\u0434\u0438\u0442\u0435 \u0441\u043D\u043E\u0432\u0430.", "error");
sessionStorage.removeItem(ADMIN_AUTH_REDIRECT_REASON_KEY);
}
const saved = localStorage.getItem(LS_TOKEN) || "";
if (!saved) return;
if (isAdminTokenExpired(saved)) {
localStorage.removeItem(LS_TOKEN);
setStatus("login", "\u0421\u0435\u0441\u0441\u0438\u044F \u0438\u0441\u0442\u0435\u043A\u043B\u0430. \u0412\u043E\u0439\u0434\u0438\u0442\u0435 \u0441\u043D\u043E\u0432\u0430.", "error");
return;
}
const payload = decodeJwtPayload(saved);
if (!payload || !payload.role || !payload.email) {
localStorage.removeItem(LS_TOKEN);
setStatus("login", "\u0421\u0435\u0441\u0441\u0438\u044F \u0438\u0441\u0442\u0435\u043A\u043B\u0430. \u0412\u043E\u0439\u0434\u0438\u0442\u0435 \u0441\u043D\u043E\u0432\u0430.", "error");
return;
}
setToken(saved);
setRole(payload.role);
setEmail(payload.email);
setUserId(String(payload.sub || ""));
}, []);
}, [isAdminTokenExpired, setStatus]);
useEffect(() => {
if (!token || !role) return;
let cancelled = false;

View file

@ -1,5 +1,6 @@
import {
DEFAULT_FORM_FIELD_TYPES,
ADMIN_AUTH_REDIRECT_REASON_KEY,
INVOICE_STATUS_LABELS,
LS_TOKEN,
OPERATOR_LABELS,
@ -1263,6 +1264,13 @@ const NEW_REQUEST_CLIENT_OPTION = "__new_client__";
}, []);
const getStatus = useCallback((key) => statusMap[key] || { message: "", kind: "" }, [statusMap]);
const isAdminTokenExpired = useCallback((rawToken) => {
const payload = decodeJwtPayload(rawToken || "");
const exp = Number(payload?.exp || 0);
if (!payload || !payload.role || !payload.email) return true;
if (!Number.isFinite(exp) || exp <= 0) return true;
return exp * 1000 <= Date.now();
}, []);
const api = useAdminApi(token);
@ -3465,6 +3473,7 @@ const NEW_REQUEST_CLIENT_OPTION = "__new_client__";
const payload = decodeJwtPayload(nextToken || "");
if (!payload || !payload.role || !payload.email) throw new Error("Не удалось прочитать данные токена");
sessionStorage.removeItem(ADMIN_AUTH_REDIRECT_REASON_KEY);
localStorage.setItem(LS_TOKEN, nextToken);
setToken(nextToken);
setRole(payload.role);
@ -3485,18 +3494,30 @@ const NEW_REQUEST_CLIENT_OPTION = "__new_client__";
);
useEffect(() => {
const authRedirectReason = sessionStorage.getItem(ADMIN_AUTH_REDIRECT_REASON_KEY) || "";
if (authRedirectReason === "expired") {
setStatus("login", "Сессия истекла. Войдите снова.", "error");
sessionStorage.removeItem(ADMIN_AUTH_REDIRECT_REASON_KEY);
}
const saved = localStorage.getItem(LS_TOKEN) || "";
if (!saved) return;
if (isAdminTokenExpired(saved)) {
localStorage.removeItem(LS_TOKEN);
setStatus("login", "Сессия истекла. Войдите снова.", "error");
return;
}
const payload = decodeJwtPayload(saved);
if (!payload || !payload.role || !payload.email) {
localStorage.removeItem(LS_TOKEN);
setStatus("login", "Сессия истекла. Войдите снова.", "error");
return;
}
setToken(saved);
setRole(payload.role);
setEmail(payload.email);
setUserId(String(payload.sub || ""));
}, []);
}, [isAdminTokenExpired, setStatus]);
useEffect(() => {
if (!token || !role) return;

View file

@ -1,3 +1,4 @@
import { ADMIN_AUTH_REDIRECT_REASON_KEY, LS_TOKEN } from "../shared/constants.js";
import { translateApiError } from "../shared/utils.js";
export function useAdminApi(token) {
@ -30,6 +31,22 @@ export function useAdminApi(token) {
if (!response.ok) {
const message = (payload && (payload.detail || payload.error || payload.raw)) || "HTTP " + response.status;
if (response.status === 401 && opts.auth !== false) {
try {
localStorage.removeItem(LS_TOKEN);
sessionStorage.setItem(ADMIN_AUTH_REDIRECT_REASON_KEY, "expired");
} catch (_) {
// noop
}
if (typeof window !== "undefined") {
const target = "/admin.html";
if (window.location.pathname !== target || window.location.search) {
window.location.replace(target);
} else {
window.location.reload();
}
}
}
const error = new Error(translateApiError(String(message)));
error.httpStatus = Number(response.status || 0);
throw error;

View file

@ -1,4 +1,5 @@
export const LS_TOKEN = "admin_access_token";
export const ADMIN_AUTH_REDIRECT_REASON_KEY = "admin_auth_redirect_reason";
export const PAGE_SIZE = 50;
export const DEFAULT_FORM_FIELD_TYPES = ["string", "text", "number", "boolean", "date"];
export const ALL_OPERATORS = ["=", "!=", ">", "<", ">=", "<=", "~"];