fix and update
This commit is contained in:
@@ -1,15 +1,38 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { Loader2 } from 'lucide-react';
|
import { Loader2 } from 'lucide-react';
|
||||||
|
import { Button } from '@/components/ui/button';
|
||||||
|
|
||||||
export function AppBootstrapScreen() {
|
export function AppBootstrapScreen({
|
||||||
|
error,
|
||||||
|
onRetry
|
||||||
|
}: {
|
||||||
|
error?: string | null;
|
||||||
|
onRetry?: () => void;
|
||||||
|
}) {
|
||||||
return (
|
return (
|
||||||
<div className="fixed inset-0 z-[120] flex min-h-screen flex-col items-center justify-center bg-white px-6">
|
<div className="fixed inset-0 z-[120] flex min-h-screen flex-col items-center justify-center bg-white px-6">
|
||||||
<Loader2 className="h-10 w-10 animate-spin text-[#3390ec]" aria-hidden />
|
{error ? (
|
||||||
<p className="mt-5 text-base font-medium text-[#101828]">Подключение к серверу</p>
|
<>
|
||||||
<p className="mt-2 max-w-sm text-center text-sm text-[#667085]">
|
<p className="max-w-md text-center text-base font-medium text-[#101828]">{error}</p>
|
||||||
Подготавливаем ваш профиль и сервисы. Это займёт несколько секунд.
|
<p className="mt-2 max-w-sm text-center text-sm text-[#667085]">
|
||||||
</p>
|
Сервер авторизации временно недоступен. Обычно помогает повтор через несколько секунд.
|
||||||
|
</p>
|
||||||
|
{onRetry ? (
|
||||||
|
<Button className="mt-6 rounded-xl" onClick={onRetry}>
|
||||||
|
Повторить подключение
|
||||||
|
</Button>
|
||||||
|
) : null}
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<Loader2 className="h-10 w-10 animate-spin text-[#3390ec]" aria-hidden />
|
||||||
|
<p className="mt-5 text-base font-medium text-[#101828]">Подключение к серверу</p>
|
||||||
|
<p className="mt-2 max-w-sm text-center text-sm text-[#667085]">
|
||||||
|
Подготавливаем ваш профиль и сервисы. Это займёт несколько секунд.
|
||||||
|
</p>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -102,7 +102,6 @@ function applySessionState(
|
|||||||
setters.activatePinLock(session.sessionId ?? '');
|
setters.activatePinLock(session.sessionId ?? '');
|
||||||
} else {
|
} else {
|
||||||
setters.clearPinLock();
|
setters.clearPinLock();
|
||||||
scheduleFedcmSessionSync();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -120,6 +119,7 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
|
|||||||
const [lockedSessionId, setLockedSessionId] = React.useState<string | null>(null);
|
const [lockedSessionId, setLockedSessionId] = React.useState<string | null>(null);
|
||||||
const [pinSubmitting, setPinSubmitting] = React.useState(false);
|
const [pinSubmitting, setPinSubmitting] = React.useState(false);
|
||||||
const [pinError, setPinError] = React.useState<string | null>(null);
|
const [pinError, setPinError] = React.useState<string | null>(null);
|
||||||
|
const [bootstrapError, setBootstrapError] = React.useState<string | null>(null);
|
||||||
const isPinLockedRef = React.useRef(false);
|
const isPinLockedRef = React.useRef(false);
|
||||||
const refreshInFlightRef = React.useRef<Promise<void> | null>(null);
|
const refreshInFlightRef = React.useRef<Promise<void> | null>(null);
|
||||||
const initialBootstrapDoneRef = React.useRef(false);
|
const initialBootstrapDoneRef = React.useRef(false);
|
||||||
@@ -182,6 +182,7 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
|
|||||||
async (accessToken: string) => {
|
async (accessToken: string) => {
|
||||||
setIsSessionReady(false);
|
setIsSessionReady(false);
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
|
setBootstrapError(null);
|
||||||
invalidateApiGatewayReady();
|
invalidateApiGatewayReady();
|
||||||
setIsApiReady(false);
|
setIsApiReady(false);
|
||||||
|
|
||||||
@@ -192,6 +193,13 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
|
|||||||
initialBootstrapDoneRef.current = true;
|
initialBootstrapDoneRef.current = true;
|
||||||
setIsApiReady(true);
|
setIsApiReady(true);
|
||||||
setIsSessionReady(true);
|
setIsSessionReady(true);
|
||||||
|
} catch (error) {
|
||||||
|
setBootstrapError(
|
||||||
|
isGatewayUnavailableError(error)
|
||||||
|
? 'Не удалось подключиться к серверу API. Подождите несколько секунд и нажмите «Повторить».'
|
||||||
|
: (getApiErrorMessage(error, 'Не удалось подключиться к серверу API') ?? 'Ошибка подключения')
|
||||||
|
);
|
||||||
|
throw error;
|
||||||
} finally {
|
} finally {
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
}
|
}
|
||||||
@@ -213,7 +221,7 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
|
|||||||
await warmUpAndApplySession(auth.accessToken);
|
await warmUpAndApplySession(auth.accessToken);
|
||||||
|
|
||||||
if (auth.pinVerified !== false) {
|
if (auth.pinVerified !== false) {
|
||||||
scheduleFedcmSessionSync(auth.accessToken, 2500);
|
scheduleFedcmSessionSync(auth.accessToken, 8000);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[clearPinLock, warmUpAndApplySession]
|
[clearPinLock, warmUpAndApplySession]
|
||||||
@@ -255,6 +263,7 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
|
|||||||
const isInitialBootstrap = !initialBootstrapDoneRef.current;
|
const isInitialBootstrap = !initialBootstrapDoneRef.current;
|
||||||
if (isInitialBootstrap) {
|
if (isInitialBootstrap) {
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
|
setBootstrapError(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -327,6 +336,10 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const message = getApiErrorMessage(error, 'Не удалось загрузить профиль');
|
const message = getApiErrorMessage(error, 'Не удалось загрузить профиль');
|
||||||
|
if (isGatewayUnavailableError(error) && isInitialBootstrap) {
|
||||||
|
setBootstrapError('Не удалось подключиться к серверу API. Подождите несколько секунд и нажмите «Повторить».');
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (message) showToast(message);
|
if (message) showToast(message);
|
||||||
logout();
|
logout();
|
||||||
return;
|
return;
|
||||||
@@ -613,7 +626,22 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
|
|||||||
logout
|
logout
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{isBootstrapping ? <AppBootstrapScreen /> : children}
|
{isBootstrapping ? (
|
||||||
|
<AppBootstrapScreen
|
||||||
|
error={bootstrapError}
|
||||||
|
onRetry={
|
||||||
|
bootstrapError
|
||||||
|
? () => {
|
||||||
|
setBootstrapError(null);
|
||||||
|
initialBootstrapDoneRef.current = false;
|
||||||
|
void refreshProfile();
|
||||||
|
}
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
children
|
||||||
|
)}
|
||||||
<PinLockModal open={isPinLocked} isSubmitting={pinSubmitting} error={pinError} onSubmit={handlePinUnlock} />
|
<PinLockModal open={isPinLocked} isSubmitting={pinSubmitting} error={pinError} onSubmit={handlePinUnlock} />
|
||||||
</AuthContext.Provider>
|
</AuthContext.Provider>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -565,9 +565,11 @@ let fedcmSyncTimer: ReturnType<typeof setTimeout> | null = null;
|
|||||||
let fedcmSyncInFlight = false;
|
let fedcmSyncInFlight = false;
|
||||||
|
|
||||||
/** Откладывает FedCM sync после OAuth/bootstrap, чтобы не конкурировать с consent/session. */
|
/** Откладывает FedCM sync после OAuth/bootstrap, чтобы не конкурировать с consent/session. */
|
||||||
export function scheduleFedcmSessionSync(accessToken?: string | null, delayMs = 1500) {
|
export function scheduleFedcmSessionSync(accessToken?: string | null, delayMs = 8000) {
|
||||||
if (typeof window === 'undefined') return;
|
if (typeof window === 'undefined') return;
|
||||||
if (window.location.pathname.startsWith('/auth/oauth/authorize')) return;
|
if (window.location.pathname.startsWith('/auth/oauth/authorize')) return;
|
||||||
|
if (window.location.pathname.startsWith('/auth/login')) return;
|
||||||
|
if (window.location.pathname.startsWith('/auth/register')) return;
|
||||||
if (fedcmSyncTimer) {
|
if (fedcmSyncTimer) {
|
||||||
clearTimeout(fedcmSyncTimer);
|
clearTimeout(fedcmSyncTimer);
|
||||||
}
|
}
|
||||||
@@ -747,17 +749,17 @@ export async function waitForStableGateway(stableProbes = GATEWAY_STABLE_PROBES)
|
|||||||
gatewayReadyAt = 0;
|
gatewayReadyAt = 0;
|
||||||
|
|
||||||
let streak = 0;
|
let streak = 0;
|
||||||
for (let attempt = 0; attempt < 40 && streak < stableProbes; attempt += 1) {
|
for (let attempt = 0; attempt < 24 && streak < stableProbes; attempt += 1) {
|
||||||
if (await probeGatewayAlive()) {
|
if (await probeGatewayAlive()) {
|
||||||
streak += 1;
|
streak += 1;
|
||||||
if (streak >= stableProbes) {
|
if (streak >= stableProbes) {
|
||||||
markApiGatewayReady();
|
markApiGatewayReady();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
await sleep(400);
|
await sleep(500);
|
||||||
} else {
|
} else {
|
||||||
streak = 0;
|
streak = 0;
|
||||||
await sleep(Math.min(500 + attempt * 220, 2200));
|
await sleep(Math.min(700 + attempt * 250, 2500));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -154,6 +154,10 @@ services:
|
|||||||
timeout: 10s
|
timeout: 10s
|
||||||
retries: 12
|
retries: 12
|
||||||
start_period: 60s
|
start_period: 60s
|
||||||
|
networks:
|
||||||
|
default: {}
|
||||||
|
idp_static:
|
||||||
|
ipv4_address: 172.28.50.10
|
||||||
|
|
||||||
media-ws:
|
media-ws:
|
||||||
pull_policy: build
|
pull_policy: build
|
||||||
@@ -302,6 +306,9 @@ services:
|
|||||||
interval: 30s
|
interval: 30s
|
||||||
timeout: 5s
|
timeout: 5s
|
||||||
retries: 3
|
retries: 3
|
||||||
|
networks:
|
||||||
|
default: {}
|
||||||
|
idp_static: {}
|
||||||
|
|
||||||
# После recreate api-gateway nginx держит старый IP upstream → 502. Этот watcher
|
# После recreate api-gateway nginx держит старый IP upstream → 502. Этот watcher
|
||||||
# делает nginx -s reload когда gateway снова поднялся (~55 с на Nest bootstrap).
|
# делает nginx -s reload когда gateway снова поднялся (~55 с на Nest bootstrap).
|
||||||
@@ -323,3 +330,9 @@ volumes:
|
|||||||
redis_data:
|
redis_data:
|
||||||
rabbitmq_data:
|
rabbitmq_data:
|
||||||
minio_data:
|
minio_data:
|
||||||
|
|
||||||
|
networks:
|
||||||
|
idp_static:
|
||||||
|
ipam:
|
||||||
|
config:
|
||||||
|
- subnet: 172.28.50.0/24
|
||||||
|
|||||||
30
install.sh
30
install.sh
@@ -33,6 +33,7 @@ INSTALL_LOCAL=0
|
|||||||
INSTALL_INTRANET=0
|
INSTALL_INTRANET=0
|
||||||
OFFLINE="${OFFLINE:-0}"
|
OFFLINE="${OFFLINE:-0}"
|
||||||
NGINX_MODE="${NGINX_MODE:-auto}"
|
NGINX_MODE="${NGINX_MODE:-auto}"
|
||||||
|
IDP_API_GATEWAY_IP="${IDP_API_GATEWAY_IP:-172.28.50.10}"
|
||||||
SKIP_DOCKER_PRUNE="${SKIP_DOCKER_PRUNE:-0}"
|
SKIP_DOCKER_PRUNE="${SKIP_DOCKER_PRUNE:-0}"
|
||||||
DOCKER_PRUNE_AGGRESSIVE="${DOCKER_PRUNE_AGGRESSIVE:-0}"
|
DOCKER_PRUNE_AGGRESSIVE="${DOCKER_PRUNE_AGGRESSIVE:-0}"
|
||||||
# Принудительный Docker Nginx (контейнер lendry-id-nginx) — включается в --fix-all
|
# Принудительный Docker Nginx (контейнер lendry-id-nginx) — включается в --fix-all
|
||||||
@@ -1565,8 +1566,16 @@ ensure_host_upstreams() {
|
|||||||
nginx_upstream() {
|
nginx_upstream() {
|
||||||
case "$1" in
|
case "$1" in
|
||||||
api)
|
api)
|
||||||
# container_name стабильнее service name при пересоздании compose-стека
|
if [[ "$NGINX_MODE" == "docker" ]]; then
|
||||||
[[ "$NGINX_MODE" == "docker" ]] && echo "http://lendry-id-api-gateway:3000" || echo "http://127.0.0.1:3000"
|
# Статический IP — nginx не кеширует DNS; переживает recreate api-gateway без reload.
|
||||||
|
if [[ -n "${IDP_API_GATEWAY_IP:-}" ]]; then
|
||||||
|
echo "http://${IDP_API_GATEWAY_IP}:3000"
|
||||||
|
else
|
||||||
|
echo "http://lendry-id-api-gateway:3000"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo "http://127.0.0.1:3000"
|
||||||
|
fi
|
||||||
;;
|
;;
|
||||||
frontend)
|
frontend)
|
||||||
[[ "$NGINX_MODE" == "docker" ]] && echo "http://lendry-id-frontend:3000" || echo "http://127.0.0.1:3002"
|
[[ "$NGINX_MODE" == "docker" ]] && echo "http://lendry-id-frontend:3000" || echo "http://127.0.0.1:3002"
|
||||||
@@ -1991,13 +2000,20 @@ nginx_upstream_var() {
|
|||||||
printf 'idp_up_%s' "$(printf '%s' "$1" | tr -c 'A-Za-z0-9' '_')"
|
printf 'idp_up_%s' "$(printf '%s' "$1" | tr -c 'A-Za-z0-9' '_')"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
nginx_api_uses_static_ip() {
|
||||||
|
[[ "$NGINX_MODE" == "docker" && -n "${IDP_API_GATEWAY_IP:-}" ]]
|
||||||
|
}
|
||||||
|
|
||||||
# proxy_pass к КОРНЮ апстрима ($request_uri сохраняет путь и query).
|
# proxy_pass к КОРНЮ апстрима ($request_uri сохраняет путь и query).
|
||||||
# docker: через resolver+переменную (переживает смену IP контейнера).
|
# docker + IDP_API_GATEWAY_IP: прямой proxy_pass без DNS (надёжно после recreate).
|
||||||
# host: статический proxy_pass на 127.0.0.1 (DNS-кеша нет, IP не меняется).
|
# docker без static IP: resolver+переменная.
|
||||||
|
# host: статический proxy_pass на 127.0.0.1.
|
||||||
render_proxy_root() {
|
render_proxy_root() {
|
||||||
local key="$1" up var
|
local key="$1" up var
|
||||||
up="$(nginx_upstream "$key")"
|
up="$(nginx_upstream "$key")"
|
||||||
if [[ "$NGINX_MODE" == "docker" ]]; then
|
if nginx_api_uses_static_ip && [[ "$key" == "api" ]]; then
|
||||||
|
printf ' proxy_pass %s$request_uri;' "$up"
|
||||||
|
elif [[ "$NGINX_MODE" == "docker" ]]; then
|
||||||
var="$(nginx_upstream_var "$key")"
|
var="$(nginx_upstream_var "$key")"
|
||||||
printf '%s\n set $%s %s;\n proxy_pass $%s$request_uri;' \
|
printf '%s\n set $%s %s;\n proxy_pass $%s$request_uri;' \
|
||||||
"$NGINX_DOCKER_RESOLVER" "$var" "$up" "$var"
|
"$NGINX_DOCKER_RESOLVER" "$var" "$up" "$var"
|
||||||
@@ -2024,7 +2040,9 @@ render_proxy_rewrite() {
|
|||||||
render_proxy_idp_api() {
|
render_proxy_idp_api() {
|
||||||
local key="api" up var
|
local key="api" up var
|
||||||
up="$(nginx_upstream "$key")"
|
up="$(nginx_upstream "$key")"
|
||||||
if [[ "$NGINX_MODE" == "docker" ]]; then
|
if nginx_api_uses_static_ip; then
|
||||||
|
printf ' rewrite ^/idp-api/?(.*)$ /$1 break;\n proxy_pass %s;' "$up"
|
||||||
|
elif [[ "$NGINX_MODE" == "docker" ]]; then
|
||||||
var="$(nginx_upstream_var "$key")"
|
var="$(nginx_upstream_var "$key")"
|
||||||
printf '%s\n set $%s %s;\n rewrite ^/idp-api/?(.*)$ /$1 break;\n proxy_pass $%s;' \
|
printf '%s\n set $%s %s;\n rewrite ^/idp-api/?(.*)$ /$1 break;\n proxy_pass $%s;' \
|
||||||
"$NGINX_DOCKER_RESOLVER" "$var" "$up" "$var"
|
"$NGINX_DOCKER_RESOLVER" "$var" "$up" "$var"
|
||||||
|
|||||||
Reference in New Issue
Block a user