From b64b7a388043e146ce47751e30a4624db359ce73 Mon Sep 17 00:00:00 2001 From: user Date: Fri, 26 Sep 2025 15:05:21 +0300 Subject: [PATCH] admin page fix --- src/app/index.tsx | 16 ++++-- src/pages/admin/index.tsx | 33 ++++++++++-- src/shared/libs/admin-auth.ts | 97 +++++++++++++++++++++++++++++++++++ 3 files changed, 136 insertions(+), 10 deletions(-) create mode 100644 src/shared/libs/admin-auth.ts diff --git a/src/app/index.tsx b/src/app/index.tsx index 4522658..080a8ba 100644 --- a/src/app/index.tsx +++ b/src/app/index.tsx @@ -13,12 +13,18 @@ export const App = () => { const [, expand] = useExpand(); useEffect(() => { - WebApp.enableClosingConfirmation(); - expand(); + if (!WebApp) { + return; + } - WebApp.setHeaderColor('#1d1d1b'); - WebApp.setBackgroundColor('#1d1d1b'); - }, []); + WebApp.enableClosingConfirmation?.(); + if (typeof expand === 'function') { + expand(); + } + + WebApp.setHeaderColor?.('#1d1d1b'); + WebApp.setBackgroundColor?.('#1d1d1b'); + }, [WebApp, expand]); return ( diff --git a/src/pages/admin/index.tsx b/src/pages/admin/index.tsx index c05abda..a638205 100644 --- a/src/pages/admin/index.tsx +++ b/src/pages/admin/index.tsx @@ -52,6 +52,29 @@ const formatDate = (iso?: string | null) => { return dateTimeFormatter.format(date); }; +const formatUnknown = (value: unknown): string => { + if (value === null || value === undefined) { + return "—"; + } + if (typeof value === "string") { + return value || "—"; + } + if (typeof value === "number" || typeof value === "bigint") { + return numberFormatter.format(Number(value)); + } + if (typeof value === "boolean") { + return value ? "Да" : "Нет"; + } + if (value instanceof Date) { + return formatDate(value.toISOString()); + } + try { + return JSON.stringify(value); + } catch (error) { + return String(value); + } +}; + const Section = ({ id, title, @@ -661,7 +684,7 @@ export const AdminPage = () => {

Окружение

{Object.entries(env).map(([key, value]) => ( - {value ?? "—"} + {formatUnknown(value)} ))}
@@ -702,8 +725,8 @@ export const AdminPage = () => { {service_config.map((item) => ( {item.key} - {item.value ?? "—"} - {item.raw ?? "—"} + {formatUnknown(item.value)} + {formatUnknown(item.raw)} ))} @@ -770,7 +793,7 @@ export const AdminPage = () => { {task.id} {task.destination || "—"} - {task.amount || "—"} + {formatUnknown(task.amount)} {task.status} {task.epoch ?? "—"} · {task.seqno ?? "—"} {task.transaction_hash || "—"} @@ -836,7 +859,7 @@ export const AdminPage = () => { {node.version || "—"} {formatDate(node.last_seen)} - {node.notes || "—"} + {formatUnknown(node.notes)} ))} diff --git a/src/shared/libs/admin-auth.ts b/src/shared/libs/admin-auth.ts new file mode 100644 index 0000000..aeab51e --- /dev/null +++ b/src/shared/libs/admin-auth.ts @@ -0,0 +1,97 @@ +const TOKEN_STORAGE_KEY = '__admin_panel_token__'; +const HEADER_STORAGE_KEY = '__admin_panel_header__'; +const COOKIE_STORAGE_KEY = '__admin_panel_cookie__'; +const DEFAULT_HEADER_NAME = 'X-Admin-Token'; +const DEFAULT_COOKIE_NAME = 'admin_session'; +const DEFAULT_MAX_AGE = 172800; // 48h + +const isBrowser = typeof window !== 'undefined'; + +export type AdminAuthSnapshot = { + token: string | null; + headerName: string; + cookieName: string; + maxAge: number; +}; + +export const getAdminAuthSnapshot = (): AdminAuthSnapshot => { + if (!isBrowser) { + return { + token: null, + headerName: DEFAULT_HEADER_NAME, + cookieName: DEFAULT_COOKIE_NAME, + maxAge: DEFAULT_MAX_AGE, + }; + } + + const token = window.localStorage.getItem(TOKEN_STORAGE_KEY); + const headerName = window.localStorage.getItem(HEADER_STORAGE_KEY) || DEFAULT_HEADER_NAME; + const cookieName = window.localStorage.getItem(COOKIE_STORAGE_KEY) || DEFAULT_COOKIE_NAME; + return { + token, + headerName, + cookieName, + maxAge: DEFAULT_MAX_AGE, + }; +}; + +export const persistAdminAuth = ({ + token, + headerName, + cookieName, + maxAge, +}: { + token: string; + headerName?: string | null; + cookieName?: string | null; + maxAge?: number | null; +}) => { + if (!isBrowser) { + return; + } + + window.localStorage.setItem(TOKEN_STORAGE_KEY, token); + if (headerName) { + window.localStorage.setItem(HEADER_STORAGE_KEY, headerName); + } + if (cookieName) { + window.localStorage.setItem(COOKIE_STORAGE_KEY, cookieName); + } + + // Best effort: mirror cookie locally to support same-origin deployments. + const targetCookieName = cookieName || DEFAULT_COOKIE_NAME; + const cookieParts = [ + `${targetCookieName}=${encodeURIComponent(token)}`, + 'Path=/', + `Max-Age=${maxAge ?? DEFAULT_MAX_AGE}`, + 'SameSite=Lax', + ]; + if (window.location.protocol === 'https:') { + cookieParts.push('Secure'); + } + document.cookie = cookieParts.join('; '); +}; + +export const clearAdminAuth = () => { + if (!isBrowser) { + return; + } + + const snapshot = getAdminAuthSnapshot(); + window.localStorage.removeItem(TOKEN_STORAGE_KEY); + window.localStorage.removeItem(HEADER_STORAGE_KEY); + window.localStorage.removeItem(COOKIE_STORAGE_KEY); + + if (snapshot.cookieName) { + const cookieParts = [ + `${snapshot.cookieName}=`, + 'Path=/', + 'Max-Age=0', + 'SameSite=Lax', + ]; + if (window.location.protocol === 'https:') { + cookieParts.push('Secure'); + } + document.cookie = cookieParts.join('; '); + } +};